纹理
定义 | 目的 | 技术 | 方法 | |
---|---|---|---|---|
纹理 | 物体表面的纹路 | 使用一张图片来控制模型的外观 | 纹理映射技术 | 逐纹素地控制模型的颜色 |
纹理映射坐标
定义 | 表示 | 归一化 | |
---|---|---|---|
纹理映射坐标 | 顶点在纹理中对应的2D坐标 | 二维变量(U,V)==UV坐标 | 【0,1】 |
/*****************************************************
//单张纹理
//使用单张纹理来作为模拟的颜色 仍使用Blinn-Phong光照模型计算光照
******************************************************/
Shader "SingleTexture"{
Properties{
_Color("Color Tint",Color) = (1,1,1,1)
_MainTex("Main Tex", 2D) = "white"{
}
_Specular("Specular", Color) = (1,1,1,1)
_Gloss("Gloss", Range(8,256)) = 10
}
SubShader{
Pass{
Tags{
"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
struct a2v{
float4 vertex : POSITION;
float4 normal : NORMAL;
float4 texcoord:TEXCOORD0; //模型的第一组纹理坐标存储到该变量中
};
struct v2f{
float4 pos :SV_POSITION;
float3 worldNormal:TEXCOORD0;
float3 worldPos:TEXCOORD1;
float2 uv:TEXCOORD2; //纹理坐标
};
float4 _Color;
float4 _Specular;
float _Gloss;
sampler2D _MainTex;
float4 _MainTex_ST;//ST: scale->translation. ST.xy:缩放值 ST.zw:偏移值
v2f vert(a2v v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
//对顶点纹理坐标进行变换
o.uv = TRANFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag(v2f i):SV_Target{
//法线方向
fixed3 wNormal = normalize(i.worldNormal);
//光照方向
fixed3 wlightdir = normalize(UnityWorldSpaceLightDir(i.worldPos));
//视角方向
fixed3 wViewdir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 halfdir = normalize(wlightdir + wViewdir );
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBINET.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(wNormal, wlightdir));
fixed3 specular = _LightColor.rgb * _Specular.rgb * pow(max(0, dot(wNormal, halfdir)), _Gloss);
fixed3 color = ambient + diffuse + specular;
return fixed4(color, 1);
}
ENDCG
}
}
Fallback "Specular"
}
tex2D(_MainTex, IN uv)
///
/// 对纹理进行采样
///
/// 需要被采样的纹理 param>
/// float2 类型的纹理坐标 param>
/// 纹素值 returns>
纹理属性 | ||||
---|---|---|---|---|
纹理类型 | 1.Texture 默认 | 2. Normal map | 3.Cubemap | |
Wrap Mode | 决定当纹理坐标超过[0,1]范围后如何平铺 | repeat 去整数取小数,纹理不断重复 |
clamp >1取1,<0取0 |
|
Filter Mode | 当纹理由于变换而发生拉伸时需要的滤波模式 效果一次增强,但性能也变大 会影响放大缩小时得到图片质量 |
point 最近邻滤波像素1个 |
Bilinear 线性滤波找4个邻近像素 |
Trilinear 同Bilinear,与多级渐远纹理混合 |
纹理缩小 | 复杂,处理抗锯齿问题 | 多级渐远纹理(mipmapping) | ||
最大尺寸 Max Size |
理想大小为2的n次幂 | |||
纹理模式 Format |
决定了Unity内部使用哪种格式来存储该纹理 | 纹理格式精度越高,占用内存越大,效果越好 | ||
Create from Grayscale | 用于从高度图中生成法线纹理 | 高度图导入后需要勾选==切线空间下的法线纹理 | ||
Bumpiness | 控制凹凸程度 | |||
Filtering | 决定使用哪种方式计算凹凸程度 | Smooth 法线纹理会比较平滑 |
Sharp Sobel率生成法线 |
使用一张纹理来修改模型表面的法线,以便为模型提供更多的细节。并不会真的改变模型的顶点位置,只是看起来好像
凹凸映射实现 | 实现方式 | 存储的内容 | 优点 | 缺陷 |
---|---|---|---|---|
高度映射 | 一张高度纹理模拟表面位移,得到修改后的法线值 | 高度值,用于表示模型表面局部的海拔高度 颜色越浅表面向外凸起 |
直观 | 计算复杂,实时计算时不能直接得到表面法线,需要由像素的灰度值计算,耗性能 |
法线映射 | 一张法线纹理直接存储表面法线 | 表面的法线方向 |
法线纹理
坐标空间 | 定义 | 法线纹理颜色 | 模-优点1 | 模优点2 | 切-优点1 | 切-优点2 | 切-优点3 | 切-优点4 |
---|---|---|---|---|---|---|---|---|
模型空间 | 模型空间中的表面法线存储在一张纹理中 | 五颜六色 所有的法线坐标都在同一个模型空间中,法线方向却不一样 |
实现简单,更加直观 计算少–不需要原始的法线切线等信息 |
提供平滑的边界 同一坐标系的法线信息,边界处插值得到的法线可以平滑变换 |
绝对法线信息-仅用于创建它时的那个模型,其他模型失效 | 得不到效果 | 同上 | 每个方向都有可能,必须存储3个方向的值,不可压缩 |
切线空间 | 模型顶点的切线空间来存储法线 切线原点–顶点本身、z轴–顶点的法线方向、x轴–顶点的切线方向、y轴–法线与切线的叉积 |
浅蓝色 表面每个点都有自己的切线空间 |
要求纹理映射连续 | 法线信息依靠纹理坐标方向的插值,可能在边缘处有缝合迹象 | 自由度高 记录的是相对法线信息,可应用于一个完全不同的网格上 |
UV动画 移动UV坐标实现凹凸移动效果 |
重用法线纹理 | 可压缩–法线的Z方向总是正方向,可由xy推导 |
/******************************************************
切线空间下计算光照模型
1.顶点着色器中把视角方向和光照方向从模型空间变换到切线空间中
2.片元着色器中通过纹理采样得到切线空间下的法线
3.与切线空间下的视角方向、光照方向等进行计算得到光照结果
*******************************************************/
Shader"TangentSpace"{
Properties{
_Color("Color Tint", Color) = (1,1,1,1)
_MainTex("Main Tex", 2D) = "white"{
}
_Specular("Specular", Color)= (1,1,1,1)
_Gloss("Gloss", Range(8,256)) = 10
_BumpMap("Normal Map", 2D) = "bump"{
} //法线纹理
_BumpScale("Bump Scale", Float) = 1.0 //控制凹凸程度,0:法线纹理不会对光照产生任何影响
}
SubeShader{
Pass{
Tags{
"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
struct a2v{
float4 vertex:POSITION;
float3 normal:NORMAL;
float4 tangent:TANGENT; //切线,w分量决定切线空间的副切线的方向
float4 texcoord:TEXCOORD0;
};
struct v2f{
float4 pos:SV_POSITION;
float3 lightdir:TEXCOORD0;//计算切线空间下的光照方向
float3 viewdir:TEXCOORD1; //计算切线空间下的视角方向
float4 uv:TEXCOORD2; //xy存储_MainTex, zw存储_BumpMap
};
float4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
float4 _Specular;
sampler2D _BumpMap;
float4 _BumpMap_ST
fixed _BumpScale;
float _Gloss;
v2f vert(a2v v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);
TANGENT_SPACE_ROTATION;
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 tLightdir = normalize(i.lightdir );
fixed3 tViewdir = normalize(i.viewdir);
fixed3 tHalf = normalize(tLightdir + tViewdir);
fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw); //法线纹理的采样
fixed3 tNormal = UnpackNormal(packedNormal); //切线空间下的法线方向
tNormal.xy *= _BumpScale;//
tNormal.z = sqrt(1.0 - saturate(dot(tNormal.xy, tNormal.xy)));//xyz啥关系呢?x^2 + y^2 + z^2 = 1
fixed3 albedo = tex2D(_MainTex, i.uv.xy).rgb * _Color.rgb;
fixed3 ambinet = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(tNormal, tLightdir));
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tNormal, tHalf)), _Gloss);
fixed3 color = ambient + diffuse + specular;
return fixed4(color, 1);
}
ENDCG
}
}
Fallback "Specular"
}
TANGENT_SPACE_ROTATION 《==》
float3 binormal = cross(normalize(v.normal), normalize(v.tangent.xyz)) * v.tangent.w; //副切线 = 法线 与 切线的叉积
float3x3 rotation = float3x3(v.tangent.xyz, binormal,v.normal);
cross(a, b)
/// < summary>
/// 计算向量积
/// summary>
UnpackNormal(packNormal)
///
/// 计算正确的法线方向
/// 法线纹理设置为Normal Map时,unity会根据平台来选择不同的压缩方法。
///
/// 法线经过映射后的像素值,利用tex2D对_BumpMap采样得到 param>
/// 法线方向 returns>
若无设置Normal Map需要手动转换
(packedNormal.xy * 2 - 1)像素[0,1]与法线[-1,1]的映射 ==>pixel = (n + 1)/2
/******************************************************
在世界空间下计算光照模型
1.顶点着色器中计算从切线空间到世界空间中的转换矩阵
2.片元着色器中把法线方向从切线空间变换到世界空间下
变换矩阵的计算:由顶点的切线、副切线和法线在世界空间下的表示得到
*******************************************************/
Shader"WorldSpace"{
Properties{
_Color("Color Tint", Color) = (1,1,1,1)
_MainTex("Main Tex", 2D) = "white"{
}
_BumpMap("Bump Map") = "bump"{
} //法线纹理
_BumpScale("Bump Scale", Float) = 1.0
_Specular("Specular", Color)=(1,1,1,1)
_Gloss("Gloss", Range(8, 256)) = 10
}
SubShader{
Pass{
Tags{
"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragme fragment frag
#include "Lighting.cginc"
struct a2v{
float4 vertex:POSITION;
float4 tangent:TANGENT;
float4 texcoord:TEXCOORD0;
float3 normal:NORMAL;
};
// 切线空间到世界空间的变换矩阵
struct v2f{
float4 pos:SV_POSITION;
float4 TtoW0:TEXCOORD0;
float4 TtoW1:TEXCOORD1;
float4 TtoW2:TEXCOORD2;
float4 uv:TEXCOORD3;
};
float4 _Color;
float4 _Specular;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _Gloss;
v2f vert(a2v v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
float3 wNormal = UnityObjectToWorldNormal(v.normal);
float3 wTangent = UnityObjetToWorldDir(v.tangent.xyz);
float3 wBinormal = cross(wNormal, wTangent) * v.tangent.w;
o.TtoW0 = float4(wTangent.x, wBinormal.x, wNormal.x, worldPos.x);
o.TtoW1 = float4(wTangent.y, wBinormal.y, wNormal.y, worldPos.y);
o.TtoW2 = float4(wTangent.z, wBinormal.z, wNormal.z, worldPos.z);
return o;
}
fixed4 frag(v2f i):SV_Target{
float3 wPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
float3 wLightdir = normalize(UnityWorldSpaceLightDir(wPos));
float3 wViewdir = normalize(UnityWorldSpaceViewDir(wPos));
float3 wHalf = normalize(wLightdir + wViewdir);
fixed3 packNormal = tex2D(_BumpMap, i.uv.zw);
fixed3 bump = UnpackNormal(packNormal);
bump.xy *= _BumpScale;
bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy)));
//将法线从切线空间转换到世界空间中。。矩阵每一行与法线点乘
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
fixed albedo = tex2D(_MainTex, i.uv.xy);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(bump, wLightdir));
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(bump, wHalf)), _Gloss);
fixed3 color = ambient + diffuse + specular ;
return fixed4(color, 1.0);
}
ENDCG
}
}
}
Unity根据不同平台对纹理进行压缩,再通过UnpackNormal函数来针对不同的压缩格式对法线纹理进行正确的采样。
压缩方式 | 纹理a通道 | g通道 | r通道 | b通道 | |
---|---|---|---|---|---|
DXT5nm | 法线x分量 | y分量 | 舍弃 | 舍弃 | z分量由xy分量推导 |
/******************************************************
使用一张渐变纹理来控制漫反射光照
*******************************************************/
Shader"RampTexture"{
Properties{
_Color("Color Tint", Color) = (1,1,11)
_RampTex("Ramp Tex", 2D) = "white"{
} //渐变纹理
_Specular("Specular", Color) = (1,1,1,1)
_Gloss("Gloss", Range(8,256)) = 10
}
SubShader{
Pass{
Tags{
"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
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;
};
float4 _Color;
float4 _Specular;
float _Gloss;
sampler2D _RampTex;
float4 _RampTex_ST;
v2f vert(a2v v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_objectToWorld, v.vertex).xyz;
o.uv = TRANSFORM_TEX(v.texcoord, _RampTex);
return o;
}
fixed4 frag(v2f i):SV_Target{
fixed3 wNormal = normalize(i.worldNormal);
fixed3 wlightdir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 wViewdir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 whalfdir = normalize(wlightdir + wViewdir);
//半兰伯特光照计算[0,1]
fixed halfLambert = 0.5*dot(wNormal, wLightdir) + 0.5;
//利用半兰伯特构建的纹理坐标对渐变纹理采样
fixed3 diffuseColor = tex2D(_RampTex, half2(halfLambert, halfLambert )).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * diffuseColor;
fixed3 specular = _LightColor.rgb * _Specular.rgb * pow(saturate(dot(wNormal, whalfdir)), _Gloss);
fixed3 color = ambient + diffuse + specular;
return fixed4(color, 1.0);
}
ENDCG
}
}
Fallback "Specular"
}
/******************************************************
使用高光遮罩纹理,逐像素的控制模型表面的高光反射强度
1.采样得到遮罩纹理的纹素值
2.使用其中某个通道的值来与某找那个表面属性进行相乘
当通道的值为0时,可以保护表面不受该属性的影响
*******************************************************/
Shader "Mask"{
Properties{
_Color("Color Tint", Color) = (1,1,1,1)
_MainTex("Main Tex", 2D) = "white" {
}
_BumpMap("Bump Map", 2D) = "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, 256)) = 10
}
SubShader{
Pass{
Tags{
"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
struct a2v{
float4 vertex : POSITION;
float3 normal :NORMAL;
float4 tangent :TANGENT;
float4 texcoord:TEXCOOR0;
};
struct v2f{
float4 pos:SV_POSITION;
float3 tViewdir:TEXCOORD0;
float3t tLightDir:TEXCOORD1;
float3 uv:TEXCOORD2;
};
float4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
sampler2D _SpecularMask;
fixed _BumpScale;
float _Gloss;
float4 _Specular;
fixed _SpecularScale;
v2f vert(a2v v){
//模型空间转换到切线空间
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
TANGENT_SPACE_ROTATION;
o.tViewdir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
o.tLightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz;
return o;
}
fixed4 frag(v2f i):SV_Target{
fixed3 tViewdir = normalize(i.tViewdir);
fixed3 tLightdir = normalize(i.tLightDir);
fixed3 tHalfdir = normalize(tViewdir+tLightdir);
//获取切线空间的法线
fixed3 tNormal = UnpackNormal(tex2D(_BumpMap, i.uv));
tNormal.xy *= _BumpScale;
tNormal.z = sqrt(1- saturate(dot(tNormal.xy, tNormal.xy)));
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
fixed3 ambinet = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * saturate(dot(tNormal, tLightdir));
//遮罩纹理采样,来控制高光反射的强度
fixed specularMask = tex2D(_SpecularMask, i.uv).r * _SpecularScale;
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tNormal, tHalfdir)), _Gloss) * specularMask;
fixed3 color = ambient + diffuse + specular;
return fixed4(color, 1.0)
}
ENDCG
}
}
Fallback "Specular"
}
是环境映射的一种实现方法。环境映射可以模拟物体周围的环境,使用了环境映射的物体可以反射出周围的环境
立方体一个有6张图像,对应了一个立方体的6张图像
环境映射立方体纹理的方法 | 方法 | 优 | 例 |
---|---|---|---|
特殊布局的纹理创建 | 1.特殊纹理类似立方体展开图的交叉布局等 2.纹理设置为Cubemap |
可以对纹理数据进行压缩,支持修正、光滑反射和HDR等 | HDR |
手动创建一个Cubemap资源 | 项目中创建一个Cubemap,再赋值6张纹理 | ||
脚本生成 | Camera.RenderToCubmap函数实现 | 不需要提前准备好立方体纹理,可以从任意位置观察到的场景图像存储到6张图像中,从而创建 |
环境映射应用 | ||
---|---|---|
立方体纹理 | 模拟出金属质感的材质 | |
反射 | 物体看起来像镀了层金属 | 1.入射光线的方向和表面法线方向计算反射方向 2.反射方向对立方体纹理采样 |
折射 | 传播方向发生改变 | 1.一种介质斜射到另一种介质中(折射率不同) N1sin = N2sin1 |
/******************************************************
模拟反射效果
1.通过入射光线的方向和表面法线的方向来计算反射方向
2.利用反射方向对立方体纹理采样
*******************************************************/
Shader "Reflection"{
Properties{
_Color("Color Tint", Color) = (1,1,1,1)
_ReflectColor("Reflect Color", Color) = (1,1,1,1) //控制反射颜色
_ReflectAmount("Reflect Amount", Range(0,1)) = 1 //控制材质的反射程度
_CubeMap("Cube Map", Cube) = "_Skybox"{
} //模拟反射的环境映射纹理
}
SubShader{
Tags {
"RenderType"="Opaque" "Queue"="Geometry"}
Pass{
Tags{
"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
struct a2v{
float4 vertex:POSITION;
float3 normal:NORMAL;
};
struct v2f{
float4 pos:SV_POSITION;
float3 worldPos:TEXCOORD0;
float3 worldNormal:TEXCOORD1;
SHADOW_COORDS(2)
};
float4 _Color;
float4 _ReflectColor;
fixed _ReflectAmount;
samplerCUBE _CubeMap;
v2f vert(a2v v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i):SV_Target{
fixed3 wNormal = normalize(i.worldNormal);
fixed3 wLightdir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 wViewdir = normalize(UnityWorldSpaceViewDir(i.worldPos));
fixed3 reflectdir = reflect(-wViewdir, wNormal);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Color.rgb * saturate(dot(wNormal, wLightdir));
fixed3 reflection = texCUBE(_CubeMap, reflectdir).rgb * _ReflectColor.rgb;
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
//漫反射与反射插值 * 衰减
fixed3 color = ambient + lerp(diffuse, reflection, _ReflectAmount) * atten;
return fixed4(color, 1.0);
}
ENDCG
}
}
FallBack"Reflective/VertexLit"
}
texCUBE(_Cubemap, wRefl)
///
/// 对立方体纹理的采样
///
/// 需要被采样的立方纹理 param>
/// 反射方向 param>
/// 纹素值 returns>
/******************************************************
模拟折射效果
1.通过入射光线的方向和表面法线的方向来计算折射方向
2.利用折射方向对立方体纹理采样
*******************************************************/
Shader"Refract"{
Prpperties{
_Color("Color Tint", Color) = (1,1,1,1)
_RefractColor("Refract Color", Color) = (1,1,1,1) //折射颜色
_RefractAmount("Refract Amount", Range(0,1)) = 1.0
_RefractRate("Refract Rate", Range(0.1, 1)) = 0.5
_Cubemap("Cube Map", Cube)="_Skybox"{
}
}
SubPass{
Tags{
"Render" = "Opaque" "Queue"="Geometry"}
Pass{
Tags{
"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
#include "AutoLight.cginc"
struct a2v{
float4 vertex:POSITION;
float3 normal:NORMAL;
};
struct v2f{
float4 pos:SV_POSITION;
float3 worldNormal:TEXCOORD0;
float3 worldPos :TEXCOORD1;
float3 worldRefract:TEXCOORD2;
float3 worldViewdir:TEXCOORD3;
SHADOW_COORDS(4)
};
float4 _Color;
float4 _RefractColor;
fixed _RefractAmount;
fixed _RefractRate;
samplerCUBE _Cubemap;
v2f vert(a2v v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(o.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.worldViewdir = UnityWorldSpaceViewDir(o.worldPos);
o.worldRefract = refract(normalize(o.worldViewdir), normalize(o.worldNormal), _RefractRate);
TRANSFER_SHADOW(o)
return o;
}
fixed4 frag(v2f i):SV_Target{
fixed3 wNormal = normlaize(i.worldNormal);
fixed3 wLightdir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Color.rgb * saturate(dot(wNormal, wLightdir));
fixed3 refraction = texCUBE(_Cubemap, o.worldRefract).rgb * _RefractColor.rgb;
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
fixed3 color = ambient + lerp(diffuse, refraction, _RefractAmount) * atten;
return fixed4(color, 1);
}
ENDCG
}
}
FallBack"Reflective/VertexLit"
}
refract(wLightdir, wNormal, Ratio)
///
/// 计算折射方向
///
/// 入射光线的方向,必须归一化 param>
/// 表面法线,必须归一化 param>
/// 入射与折射的介质折射率之比 param>
/// 折射方向,模==入射光线的模 returns>
描述了一种光学现象,即当光线照射到物体表面上时,一部分发生反射,一部分进入物体内部,发生折射或者散射。被反射的光和入射光之间存在一定的比率关系,==>菲涅耳等式可以计算
/******************************************************
使用Schlick 菲涅尔近似模拟菲涅尔反射
1.利用Schlick计算fresnel变量
2.使用Fresnel来混合漫反射光照和反射光照
*******************************************************/
Shader"Frenel"{
Properties{
_Color("Color Tint", Color) = (1,1,1,1)
_FresnelScale("Fresnel Scale", Range(0,1)) = 0.5
_Cubemap("Cube Map", Cube) = "_Skybox"{
}
}
SubShader{
Tags{
"RenderType"="Opaque" "Queue"="Geometry"}
Pass{
Tags{
"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#pragma multi_compile_fwdbase
#include "Lighting.cginc"
#include "AutoLight.cginc"
struct a2v{
float4 vertex:POSITION;
float3 normal:NORMAL;
};
struct v2f{
float4 pos:SV_POSITION;
float3 worldNormal:TEXCOORD0;
float3 worldPos:TEXCOORD1;
float3 worldReflect:TEXCOORD2;
float3 worldView:TEXCOORD3;
SHADOW_COORDS(4)
};
float4 _Color;
fixed _FresnelScale;
samplerCUBE _Cubemap;
v2f vert(a2v v){
a2v o;
o.pos = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
o.worldView = UnityWorldSpaceViewDir(o.worldPos);
o.worldReflect = reflect(-o.worldView, o.worldNormal);
TRANSFER_SHADOW(o);
return o;
}
fixed4 frag(v2f i):SV_Target{
fixed3 wNormal = normalize(i.worldNormal);
fixed3 wLightdir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 wViewdir = normlaize(i.);
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Color.rgb * saturate(dot(wNormal, wLightdir));
fixed fresnel = _FresnelScale + (1-_FresnelScale)*(pow((1 -dot(wViewdir , wNormal)), 5));
fixed3 reflectdir = texCUBE(_Cubemap, i.worldReflect).rgb;
UNITY_LIGHT_ATTENUATION(atten, i, i.wordlPos);
fixed3 color = ambient + lerp(diffuse, reflectdir , saturate(fresnel ))*atten;
return fixed4(color, 1);
}
ENDCG
}
}
Fallback "Reflective/VertexLit"
}
方式 | 做法 | 优点 |
---|---|---|
1 | 1.Project目录下创建一个渲染纹理 2.把某个摄像机的渲染目标设置成渲染纹理 3.渲染结果会实时更新到渲染纹理中,而不会显示在屏幕上 |
可以选择渲染纹理的分辨率、滤波模式等纹理属性 |
2 | 1.屏幕后处理时使用GrabPass命令或OnRenderImage函数来获取当前屏幕图像 2.屏幕图像放在一张和屏幕分辨率等同的渲染纹理中 3.在自定义的Pass中把它们当成普通的纹理处理,实现屏幕特效 |
/******************************************************
使用渲染纹理模拟镜子效果
*******************************************************/
Shader"Mirror"{
Properties{
_MainTex("Main Tex", 2D) = "white"{
}
}
SubShader{
Tags{
"RenderType"="Opaque" "Queue"="Geometry"}
Pass{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct a2v{
float4 vertex:POSITION;
float3 texcoord:TEXCOOR0;
};
struct v2f{
float4 pos:SV_POSITION;
float3 uv:TEXCOORD0;
};
sampler2D _MainTex;
float4 _MainTex_ST;
v2f vert(a2v v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord;
o.uv.x = 1- v.texcoodr.x;
return o;
}
fixed4 frag(v2f i):SV_Target{
return tex2D(_MainTex, i.uv);
}
ENDCG
}
}
Fallback Off
}
/******************************************************
使用渲染纹理模拟玻璃效果
1.使用一张法线纹理来修改模型的法线信息
2.通过一个Cubemap来模拟玻璃的反射。
3.使用GrabPass获取玻璃后面的屏幕图像,并使用切线空间下的法线对屏幕纹理坐标偏移
4.对屏幕图像的采样来模拟近似的折射效果
*******************************************************/
Shader "GlassRefrction"{
Properties{
_MainTex("MainTex", 2D) ="white"{
} // 玻璃的材质纹理
_BumpMap("Normal Map", 2D)="white"{
} // 玻璃的法线纹理
_CubeMap("Environment CubeMap", Cube) = "_Skybox"{
} //模拟反射的环境纹理
_Distortion("Distortion", Range(0,100)) = 10 // 控制模拟反射的扭曲程度
_RefractAmount("Refract Amount", Range(0.0,1.0)) = 1.0 //控制折射程度
}
SubShader{
Tags{
"RenderType" = "Opaque" "Queue"="Transparent"} //玻璃是透明的,需要把所有不透明的先渲染
GrabPass{
"_RefractionTex"} //抓取屏幕图像的Pass
Pass{
Tags{
"LightMode" = "ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
struct a2v{
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float3 texcoord:TEXCOORD0;
};
struct v2f{
float4 pos: SV_POSITION;
float4 iToW0:TEXCOOR0;
float4 iToW1:TEXCOOR1;
float4 iToW2:TEXCOOR2;
float4 uv:TEXCOORD3;
float4 scrPos:TEXCOORD4;
};
float4 _MainTex_ST ;
sampler2D _MainTex;
sampler2D _BumpMap;
float4 _BumpMap_ST;
sampler2D _RefractionTex;
float4 _RefractionTex_TexelSize; //纹理的纹素大小
samplerCUBE _CubeMap;
float _Distortion;
fixed _RefractAmount;
v2f vert(a2v v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
float3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
float3 worldBinal = cross(worldNormal, worldTangent) * v.tangent.w;
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);
//存储顺序:副切线、切线、法线
o.iToW0 = float4(worldTangent.x, worldBinal.x, worldNormal.x, worldPos.x);
o.iToW1 = float4(worldTangent.y, worldBinal.y, worldNormal.y, worldPos.y);
o.iToW2 = float4(worldTangent.z, worldBinal.z, worldNormal.z, worldPos.z);
o.scrPos = ComputerGrapScreenPos(o.pos);
return o;
}
fixed4 frag(v2f i):SV_Target{
fixed3 worldPos = fixed3(i.iToW0.w, i.iToW1.w, i.iToW2.w);
fixed3 lightdir = Normalize(UnityWorldSpaceLightDir(worldPos));
fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
float2 offest = bump.xy*_Distortion*_RefractionTex_TexelSize.xy;
i.scrPos.xy = offset +i.scrPos.xy;
//透视除法获取真正的屏幕坐标,采样、模拟折射颜色
fixed3 refraColor = tex2D(_RefractionTex, i.scrPos.xy/i.scrPos.w).rgb;
bump = normalize(half3(dot(i.iToW0.xyz, bump), dot(i.iToW1.xyz, bump), dot(i.iToW2.xyz, bump)));
fixed3 refldir = reflect(-lightdir, bump );
//反射颜色
float4 texColor = tex2D(_MainTex, i.uv.xy);
fixed3 reflCor = texCUBE(_CubeMap, refldir).rgb *texColor.rgb;
fixed3 final =reflCor * (1-_RefractAmount) + refraColor * _RefractAmount;
return fixed4(final, 1.0);
}
ENDCG
}
}
}
ComputerGrapScreenPos(pos)
///抓取屏幕图像的采样坐标
/// 顶点变换后的坐标
GrabPass | 后续Pass访问屏幕图像 | 不同点 | |
---|---|---|---|
GrabPass{} | _GrabTexture | 多个物体抓取时,性能消耗大,每一个物体单独进行 | 每个物体的得到不同的屏幕图像 |
GrabPass{“Texture”} | 自定义的Texture | 每一帧只会执行一次抓取 | 所有物体使用同一张屏幕图像 |
模拟实时水面 | 高度图,不断修改水面的法线方向 |
模拟水流 | 时间相关变量对噪声纹理采样,利用法线信息,进行正常的反射+折射 |
/******************************************************
*水波效果
1.立方体纹理作为环境纹理,模拟反射
2.使用GrapPass获取当前屏幕的渲染纹理,并使用切线空间下的法线方向对像素的屏幕坐标偏移
3.对渲染纹理进行屏幕采样--模拟近似的折射效果
* WaterWave.Shader
*****************************************************/
Shader "WaterWave"{
Properties{
_Color("Main Tint", Color) = (0,1,0,1) //控制水面颜色
_MainTex("Main Tex", 2D) = "white"{
} //水面波纹材质纹理
_WaveMap("Wave Map", 2D)="bump"{
} //噪声纹理生成的法线纹理
_CubeMap("Cube Map", Cube) = "_Skybox"{
} //环境纹理
_WaveXSpeed("Wave X Speed", Range(-0.1,0.1)) = 0.01 //法线纹理在X方向上的平移速度
_WaveYSpeed("Wave Y Speed", Range(-0.1,0.1)) = 0.01 //法线纹理在Y方向上的平移速度
_Distortion("Distortion", Range("0, 100")) 10 //控制模拟折射时图像的扭曲程度
}
SubShader{
//设置渲染队列
Tags{
"Queue" = "Transparent" "RenderType"="Opaque"}
//抓取屏幕图像
GrabPass{
"_RefractionTex"}
Pass
{
CGPROGRAM
#include "UnityCG.cginc"
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _WaveMap;
float4 _WaveMap_ST;
samplerCUBE _CubeMap;
fixed _WaveXSpeed;
fixed _WaveYSpeed;
float _Distortion;
sampler2D _RefractionTex;
float4 _RefractionTex_TexelSize;
struct v2f{
float4 pos : SV_POSITION;
float4 scrPos :TEXCOORD0;
float4 uv :TEXCOORD1;
float4 TtoW0 : TEXCOORD2;
float4 TtoW1 : TEXCOORD3;
float4 TtoW2 : TEXCOORD4;
};
v2f vert(appdata_base v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord, _WaveMap);
//获取抓取屏幕图像的采样坐标
o.scrPos = ComputeGrabScreenPos(o.pos);
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent,xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
//法线 切线 副切线
o.TtoW0 = fixed4(worldNormal.x, worldTangent.x, worldBinormal.x, worldPos.x);
o.TtoW1 = fixed4(worldNormal.y, worldTangent.y, worldBinormal.y, worldPos.y);
o.TtoW2 = fixed4(worldNormal.z, worldTangent.z, worldBinormal.z, worldPos.z);
return o;
}
fixed4 frag(v2f i):SV_Target{
float3 worldPos = float3(i.TtoW0.x, i.TtoW1.y, i.TtoW2.z);
float3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
float2 speed = _Time.y * float2(_WaveXSpeed, _WaveYSpeed);
//获取切线空间的法线
fixed3 bump1 = UnpackNormal(tex2D(_WaveMap, i.uv.zw + speed)).rgb;
fixed3 bump2 = UnpackNormal(tex2D(_WaveMap, i.uv.zw - speed)).rgb;
fixed3 bump = normalize(bump1 + bump2);
float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy;
//透视除法,模拟折射颜色
fixed3 refrCol = tex2D(_RefractionTex, i.scrPos.xy/i.scrPos.w).rgb;
//将法线方向从切线空间转换到世界坐标中
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
fixed4 texColor = tex3D(_MainTex, i.uv.xy + speed);
//反射方向
fixed3 refDir = reflect(-viewDir, bump);
fixed3 relCol = texCUBE(_CubMap, reflDir).rgb * texColor.rgb *_Color.rgb;
//菲涅尔系数
fixed fresnel = pow(1-saturate(dot(viewDir, bump)), 4);
fixed3 finalColor = refrCol * fresnel + refrCol * (1 - fresnel);
return fixed4(finalColor, 1.0);
}
ENDCG
}
}
Fallback Off
}
由计算机生成的图像。由特定的算法来创建个性化图像或非常真实的自然元素。
好处:可以使用各种参数来控制纹理的外观,可以得到更加丰富的动画和视觉效果。
/**********************************
程序纹理
生成波点纹理
***********************************/
[ExecuteInEditMode]
public class TextureGeneration:MonoBehaviour{
public Material material = null;
private Texture2D _generateTexture = null;
#region 定义材质的属性
private float _textureWidth = 512f;
public float TextureWidth{
get {
return _textureWidth;} set{
_textureWidth = value;}
}
//背景颜色
private Color _backcolor = Color.white;
public Color BackColor {
get{
return _backcolor;} set{
_backcolor = value;}
}
//波点颜色
private Color _circlecolor = Color.black;
public Color CircleColor{
get{
return _circlecolor;}set{
_circlecolor = value;}
}
//模糊系数
private float _blurFactor = 2.0f;
public float BlurFactor{
get{
return _blurFactor;} set{
_blurFactor = value;}
}
#endregion
protected void Start(){
if(material == null){
Renderer render = gameObject.GetComponent<Renderer>();
if(render == null) return;
material = render.sharedMaterial;
}
UpdateMaterial();
}
public void UpdateMaterial(){
if (null == material) return ;
_generateTexture = GenerateProcedureTexture();
material.SetTexture("_MainTex", _generateTexture);
}
publuc Texture2D GenerateProcedureTexture(){
//贴图
Texture2D texture = new Texture2D(TextureWidth, TextureWidth);
//圆与圆之间的距离
float circleInterval = TextureWidth/5.0f;
float radius = TextureWidth/10.f;
for(int w = 0; w < TextureWidth; ++w){
for(int h = 0; h < TextureWidth; ++h )
{
Color pixel = BackColor ;
for(int i = 0; i < 4; i++){
for (int j = 0; j < 4 ; j++){
//计算圆心的位置
Vector2D circleCenter = new Vector2D(circleInterval*(i + 1), circleInterval*(j + 1));
//计算当前像素与圆心的距离-半径
float dist = Vector2D.Distance(new(w,h), circleCenter) - radius;
Color mixColor = MixColor(CircleColor, new Color(pixel.r, pixel.g, pixel.b, 0.0f), Mathf.SmoothStep(0f, 1f, dist * BlurFactor));
//与之前的颜色进行混合
pixel = MixColor (pixel, mixColor, mixColor.a);
}
}
texture.SetPixel(w,h,pixel);
}
}
//像素值写入纹理中
texture.Apply();
return texture;
}
public Color MixColor(Color circle, Color Color2, float mixFactor){
Color mixColor = Color.white;
mixColor.r = Mathf.Lerp(circle.r, color2.r, mixFactor);
mixColor.g = Mathf.Lerp(circle.r, color2.r, mixFactor);
mixColor.b = Mathf.Lerp(circle.r, color2.r, mixFactor);
mixColor.a = Mathf.Lerp(circle.r, color2.r, mixFactor);
return mixColor;
}
}
Mathf.SmoothStep(from : float,to : float, t : float) : float
///
///在最小和最大值之间的插值,并在限制处渐入渐出
///
///最小值
/// 最大值
///插值
Unity Mathf 数学运算(C#)
软件 Substance Designer
总结:
效果 | 实现方式 | 优化 | 缺陷 |
---|---|---|---|
镜子效果 | 渲染纹理 | 图像模糊不清时可使用更高的分辨率或抗锯齿采样 | 更高的分辨率会影响带宽跟性能 |
纹理 | 采样 | 缺点 | ||
---|---|---|---|---|
凹凸纹理 | 修改模型表面的法线 | 二维 | ||
渐变纹理 | 1.控制漫反射效果 2.插画风格的渲染效果 |
二维 | ||
遮罩纹理 | 1.控制模型表面的光照效果 2.混合多张纹理 |
可以充分利用遮罩纹理中的每个颜色通道存储不同的表面属性 | 二维 | |
立方体纹理 | 环境映射的实现方法 | 实现简单快速,效果好 | 三维的纹理坐标2 | 1.引入新物体或物体发生移动时,需要重新生成立方体纹理 2.不能模拟多次反射结果,仅反射环境 |
渲染纹理 |