目次
1. 紫外線とは
3D モデルの場合、最も重要な座標系が 2 つあります。1 つは頂点の位置 (X、Y、Z) 座標であり、もう 1 つは UV 座標です。UVとは何ですか? 簡単に言えば、これはテクスチャをモデルの表面にマッピングするための基礎となります。完全にするには、UVW にする必要があります (XYZ がすでに使用されているため、別の 3 文字を選択して示します)。UとVはディスプレイの水平方向と垂直方向の画像の座標であり、値は通常0〜1、つまり(水平方向のU番目のピクセル/画像の幅、垂直方向のV番目のピクセル)です。方向/画像の高さ)。Wはどうですか?テクスチャは 2 次元ですが、どうして 3 つの座標があるのでしょうか? W の方向はディスプレイの表面に対して垂直です。これは通常、プロシージャル マッピングまたは 3D マッピング テクノロジ (確かに 3D マッピングの概念があることを思い出してください!) に使用されますが、ゲームでは一般的に使用されません。通常、これを短縮して UV と呼びます。
テクスチャを定義します。
Properties
{
// 主纹理
_MaxTex ("_MaxTex",2d) = "white" {}
}
sampler2D _MaxTex;
float4 _MaxTex_ST;
_MainTex_ST: テクスチャのオフセット スケーリング属性を示します。プロパティ パネルにティリングとオフセットを示します。float4 型の xy はスケーリングを格納し、zw はオフセットを格納します。
_MainTex_ST *_MainTex_ST.xy+_MainTex_ST.zw により UV texcord を再計算します
または、組み込みメソッド TRANSFORM_TEX(uv,_MainTex) を使用します。
tex2D(_MainTex,uv) は _MainTex をサンプリングして、UV 座標の下の色を取得します。
完全なコード: これは拡散反射とハイライトを使用したコードです。理解できない場合は、次のコードを参照してください:拡散反射、ハイライト反射
Shader "Unlit/005"
{
Properties
{
// 漫反射颜色
_Diffuse ("Diffuse",Color) = (1,1,1,1)
// 高光反射颜色值
_Specular ("Specular",Color) = (1,1,1,1)
// 高光反射值
_Gloss ("_Gloss",range(1,100)) = 5
// 主纹理
_MaxTex ("_MaxTex",2d) = "white" {}
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
sampler2D _MaxTex;
float4 _MaxTex_ST;
float4 _Specular;
float4 _Diffuse;
float _Gloss;
struct v2f
{
float4 vertex : POSITION;
float2 uv : TEXCOORD1;
float3 worldNormal : TEXCOORD0;
float3 viewDir : TEXCOORD2;
};
v2f vert (appdata_base v)
{
v2f o;
// 将对象空间中的点变换到齐次坐标中的摄像机裁剪空间
o.vertex = UnityObjectToClipPos(v.vertex);
// 计算uv坐标
o.uv = TRANSFORM_TEX(v.texcoord,_MaxTex);
// o.uv = v.texcoord.xy * _MaxTex_ST.xy + _MaxTex_ST.zw;
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.viewDir = normalize(WorldSpaceViewDir(o.vertex));
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 纹理采样
float3 texColor = tex2D(_MaxTex,i.uv);
// 计算漫反射
float3 diffuse = texColor * _LightColor0.rgb * _Diffuse.rgb * (dot(_WorldSpaceLightPos0.xyz,i.worldNormal) * 0.5 + 0.5);
// 计算高光
float3 halfVector = normalize(normalize(_WorldSpaceLightPos0.xyz) + i.viewDir);
// 计算高光
float3 specular = texColor * _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(halfVector,i.worldNormal)),_Gloss);
return float4(diffuse + specular,1);
}
ENDCG
}
}
}
2.バンプテクスチャ
バンプ テクスチャの本質は、頂点法線座標を保存する画像を通じてバンプ エフェクトを作成することであり、モデル自体は変更されていません。
法線を変更するとでこぼこした結果が生じる理由は、法線の機能は光を計算することであるためです。実生活では、光がオブジェクトに影を落とし、区別があるため、目に見えるオブジェクトは非常に 3 次元に感じられます。バンプマップは法線を変えることで間接的に光の向きや影の出来が変化するので、立体的に見えると思います。
バンプ テクスチャには 2 つのタイプがあります。
-
高さマップ:高さマップには、頂点の法線ではなく、ピクセルの高度を示す強度値が格納されます。色が暗いほど、凹面が大きくなります。これは、上で示したように、凹面が大きいほど見えにくくなるためです。高さマップは、白い領域の高さが高く、暗い領域の高さが低くなります。グレー値に従って表面法線計算に転送する必要がありますが、これはより複雑で、役に立ちません。アート制作まで。
-
法線マップ: 頂点の法線座標を格納します。
法線テクスチャには面の法線方向が格納されます。法線方向の各成分の範囲は [ − 1 , 1 ] の間、ピクセルの成分の範囲は [ 0 , 1 ] の間であるため、マッピングする必要があります
ピクセル=通常*0.5+0.5
したがって、シェーダーで通常のテクスチャをサンプリングした後、結果に対して逆マッピングを実行する必要があります。
通常=ピクセル*2−1
法線マップのほとんどが水色なのはなぜですか?
水色は、変更された法線マップが接線空間に法線を格納していることを意味します。頂点の元の法線は (0, 0, 1) で、水色のピクセル上の RGB (0.5, 0.5, 1) にマッピングされます。
法線マップには 2 つの種類があります。
-
モデル空間の法線座標
-
接空間の法線座標
モデル空間法線:
利点: すべての法線が同じ座標空間内にあるため、空間変換を行わずにモデルの法線を直接取得できるため、コーナーでの線形補間によって得られる効果がより滑らかになります。
短所: モデル空間には絶対法線が保存されているため、法線を作成するモデルにのみ適用でき、モデルを変更すると正しい結果が得られず、UV オフセットを実行できません。
接空間の法線
- 自由度が高くなります。
- 接線空間の法線テクスチャは、異なるメッシュ上でも妥当な結果を与える相対的な法線テクスチャです。
- UV アニメーションを実行でき、テクスチャの UV 座標を移動することでバンプの動きの効果を実現できます。
- 通常のテクスチャは再利用できます。
- 接線空間では、z 軸は常に正の方向にあり、z 座標は xy = sqrt(1-max(0,dot(xy,xy))) から求めることができます。
どのように達成するか?
照明を計算するには、接線空間またはワールド空間のいずれかで座標空間を統一する必要があります。
接線空間:光源の方向と視線の方向を頂点の接線空間に変換し、フラグメントで照明モデルを計算します。
ワールド空間:法線をワールド空間に変換します。キューブマップの環境をサンプリングするなど、ワールド空間で法線座標と光の方向を使用する必要がある場合があります。テクスチャ座標はピクセルごとであるため、次のようにする必要があります。フラグメント内の色 デバイス内でサンプリングしてから、上記よりも 1 つ多い行列演算である行列変換を実行します。
まず、接線空間に次のテクスチャ マッピングを実装しましょう。
次に、光源の方向と視野角の方向を接空間に変換する必要がありますが、その変換に必要な回転行列を求める必要があります。私たちが知っているのは、変換された 3 つのベクトルです。
- b = (0,1,0)
- t = (1,0,0)
- n = (0,0,1)
変換前の 3 つのベクトルが次のとおりであるとします。
- b′ = ( xb,yb,zb)
- t' = (xt,yt,zt)
- n' = (xn,yn,zn)
変換行列は次のとおりです。
c1 = t' 、 c2 = b' 、 c3 = n' が得られます。
シェーダコード:
// サブタンジェントベクトルを計算 float3 biNormal =cross(normalize(v.normal),normalize(v.tangent.xyz))*v.tangent.w; // 回転行列を計算 float3x3 Rotation = float3x3(v.tangent.xyz 、biNormal、v.normal);
Unity の組み込みのカスタム書き込みメソッドを使用できます
TANGENT_SPACE_ROTATION
UnityCG.cginc では、上で取得した接線空間回転変換行列を記述するのに実際に役立ちます。
Shader "Unlit/006"
{
Properties
{
// 漫反射颜色
_Diffuse ("Diffuse",Color) = (1,1,1,1)
// 高光反射颜色值
_Specular ("Specular",Color) = (1,1,1,1)
// 高光反射值
_Gloss ("_Gloss",range(1,100)) = 5
// 主纹理
_MaxTex ("MaxTex",2d) = "white" {}
// 法线纹理
_BumpMap ("Bump Map",2d) = "white" {}
// 控制凹凸程度
_BumpScale ("Bump Scale",float) = 1
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
sampler2D _MaxTex;
float4 _MaxTex_ST;
float4 _Specular;
float4 _Diffuse;
float _Gloss;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _BumpScale;
struct v2f
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float2 normalUv : TEXCOORD1;
float3 lightDir : TEXCOORD2;
float3 viewDir : TEXCOORD3;
};
v2f vert (appdata_tan v)
{
v2f o;
// 将对象空间中的点变换到齐次坐标中的摄像机裁剪空间
o.vertex = UnityObjectToClipPos(v.vertex);
// 计算uv坐标
o.uv = TRANSFORM_TEX(v.texcoord,_MaxTex);
o.normalUv = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
// 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);
// 求切线空间的光源方向
o.lightDir = normalize(mul(rotation,UnityWorldToObjectDir(_WorldSpaceLightPos0.xyz)));
// 切线空间视角方向
o.viewDir = normalize(mul(rotation,ObjSpaceLightDir(v.vertex)));
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 主纹理采样
fixed4 texColor = tex2D(_MaxTex,i.uv);
// 法线贴图采样
fixed4 packedNormal = tex2D(_BumpMap,i.normalUv);
fixed3 tangentNormal;
tangentNormal.xy = (packedNormal.xy * 2 - 1 ) * _BumpScale;
tangentNormal.z = sqrt(1 - max(0,dot(tangentNormal.xy,tangentNormal.xy)));
// 用内置方法 UnpackNormal 图片类型为NormalMap
//fixed3 tangentNormal = UnpackNormal(packedNormal);
// tangentNormal.xy *= _BumpScale;
// 计算漫反射
float3 diffuse = texColor.rgb * _LightColor0.rgb * _Diffuse.rgb * (dot(i.lightDir,tangentNormal) * 0.5 + 0.5);
// 计算高光
float3 halfVector = normalize(i.lightDir + i.viewDir);
// 计算高光
float3 specular = texColor.rgb * _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(halfVector,tangentNormal)),_Gloss);
return float4(diffuse + specular,1);
}
ENDCG
}
}
}
メインのテクスチャ マップと法線マップを指定します。法線マップは PhotoShop でフィルター -> 3D -> 法線マップの生成でメイン テクスチャを使用して作成できますが、次のプロジェクトは通常アーティストによって与えられます
シェーダ効果は左側が法線なし、右側が法線テクスチャとなっており、法線テクスチャを追加した方がより凹凸になっていることがわかります。
ワールド空間テクスチャ マッピングを使用して次のことを行っています。
頂点シェーダーでタンジェント空間からワールド空間への変換行列を計算する必要がありますが、補間レジスタには最大で float4 サイズの変数しか格納できないため、行列などの変数については 3 つの float3 変数を定義する必要があります。ただし、補間レジスタの空間を最大限に活用するために、補間レジスタを float4 型として定義し、ワールド空間での頂点位置を格納するために追加の次元を使用できます。uv 変数は float4 型として定義することもできます。この場合、xy コンポーネントはマップ テクスチャ座標の格納に使用され、zw コンポーネントは通常のテクスチャ座標の格納に使用されます。
コードを直接アップロードします。コード内のすべての詳細なメモは次のとおりです。
Shader "Unlit/007"
{
Properties
{
// 漫反射颜色
_Diffuse ("Diffuse",Color) = (1,1,1,1)
// 高光反射颜色值
_Specular ("Specular",Color) = (1,1,1,1)
// 高光反射值
_Gloss ("_Gloss",range(1,100)) = 5
// 主纹理
_MaxTex ("MaxTex",2d) = "white" {}
// 法线纹理
_BumpMap ("Bump Map",2d) = "white" {}
// 控制凹凸程度
_BumpScale ("Bump Scale",float) = 1
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
sampler2D _MaxTex;
float4 _MaxTex_ST;
float4 _Specular;
float4 _Diffuse;
float _Gloss;
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _BumpScale;
struct v2f
{
float4 vertex : POSITION;
float4 uv : TEXCOORD0;
float4 T2w0 : TEXCOORD1;
float4 T2w1 : TEXCOORD2;
float4 T2w2 : TEXCOORD3;
};
v2f vert (appdata_tan v)
{
v2f o;
// 将对象空间中的点变换到齐次坐标中的摄像机裁剪空间
o.vertex = UnityObjectToClipPos(v.vertex);
// 计算uv坐标
o.uv.xy = TRANSFORM_TEX(v.texcoord,_MaxTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord,_BumpMap);
float2 normalUv = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldBiTangent = cross(worldTangent,worldNormal) * v.tangent.w;
fixed3 worldPos = UnityObjectToWorldDir(v.vertex.xyz);
o.T2w0 = float4(worldTangent.x,worldBiTangent.x,worldNormal.x,worldPos.x);
o.T2w1 = float4(worldTangent.y,worldBiTangent.y,worldNormal.y,worldPos.y);
o.T2w2 = float4(worldTangent.z,worldBiTangent.z,worldNormal.z,worldPos.z);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 主纹理采样
fixed4 texColor = tex2D(_MaxTex,i.uv.xy);
// 法线贴图采样
fixed4 packedNormal = tex2D(_BumpMap,i.uv.zw);
//fixed3 tangentNormal;
//tangentNormal.xy = (packedNormal.xy * 2 - 1 ) * _BumpScale;
//tangentNormal.z = sqrt(1 - max(0,dot(tangentNormal.xy,tangentNormal.xy)));
// 用内置方法 UnpackNormal 图片类型为NormalMap
fixed3 tangentNormal = UnpackNormal(packedNormal);
tangentNormal.xy *= _BumpScale;
// 将切线空间法线变换到世界空间
fixed3 worldNormal = normalize(fixed3(dot(i.T2w0.xyz,tangentNormal),dot(i.T2w1.xyz,tangentNormal),dot(i.T2w2.xyz,tangentNormal)));
// 计算漫反射
float3 diffuse = texColor.rgb * _LightColor0.rgb * _Diffuse.rgb * (dot(_WorldSpaceLightPos0.xyz,worldNormal) * 0.5 + 0.5);
// 计算高光
float3 viewDir = _WorldSpaceCameraPos.xyz - float3(i.T2w0.z,i.T2w1.z,i.T2w2.z);
float3 halfVector = normalize(_WorldSpaceLightPos0.xyz + viewDir);
// 计算高光
float3 specular = texColor.rgb * _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(halfVector,worldNormal)),_Gloss);
return float4(diffuse + specular,1);
}
ENDCG
}
}
}
影響は次のとおりです: ライン マップ、タンジェント空間テクスチャ マッピング、ワールド空間テクスチャ マッピングができなくなります。
3. グラデーションテクスチャマッピング
テクスチャは、オブジェクトの色を定義するためだけでなく、任意の表面のプロパティを保存するためにも使用できます。一般的な使用法は、グラデーション テクスチャを使用して拡散照明の結果を制御することです。テクスチャ サンプリングには寒色から暖色へのグラデーション イメージを使用し、サンプリング結果を使用して拡散モデルを計算します。イラスト風の描写効果が得られ、従来の拡散反射よりも物体の輪郭が鮮明になります。この手法は、多くの漫画スタイルのレンダリングで使用されます。
Shader "Unlit/008"
{
Properties
{
// 漫反射颜色
_Diffuse ("Diffuse",Color) = (1,1,1,1)
// 高光反射颜色值
_Specular ("Specular",Color) = (1,1,1,1)
// 高光反射值
_Gloss ("_Gloss",range(1,100)) = 5
// 主纹理
_MaxTex ("_MaxTex",2d) = "white" {}
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
sampler2D _MaxTex;
float4 _MaxTex_ST;
float4 _Specular;
float4 _Diffuse;
float _Gloss;
struct v2f
{
float4 vertex : POSITION;
float3 worldNormal : TEXCOORD0;
float3 viewDir : TEXCOORD2;
};
v2f vert (appdata_base v)
{
v2f o;
// 将对象空间中的点变换到齐次坐标中的摄像机裁剪空间
o.vertex = UnityObjectToClipPos(v.vertex);
// 计算uv坐标
//o.uv = TRANSFORM_TEX(v.texcoord,_MaxTex);
// o.uv = v.texcoord.xy * _MaxTex_ST.xy + _MaxTex_ST.zw;
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.viewDir = normalize(WorldSpaceViewDir(o.vertex));
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 半兰伯特漫反射
float halfLambert = (dot(_WorldSpaceLightPos0.xyz,i.worldNormal) * 0.5 + 0.5);
// 采样渐变纹理 使用半兰伯特做uv坐标
float3 texColor = tex2D(_MaxTex,float2(halfLambert,halfLambert));
float3 diffuse = texColor * _LightColor0.rgb * _Diffuse.rgb * texColor;
// 计算高光
float3 halfVector = normalize(normalize(_WorldSpaceLightPos0.xyz) + i.viewDir);
// 计算高光
float3 specular = texColor * _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(halfVector,i.worldNormal)),_Gloss);
return float4(diffuse + specular,1);
}
ENDCG
}
}
}
効果:
4.マスクテクスチャ
ゲーム内の地形シーンでは草地と砂漠の境界があることを想定しており、それぞれの領域の反射値を保護するためにマスクテクスチャを使用します。草地にはハイライトが必要であると想定しています。 、砂漠にはハイライトは必要ありません。
実装は非常に簡単で、マスクテクスチャを追加し、白のRGB値は255/255=1なので、ハイライト部分は白、不要な場所は黒になります。通常、必要なチャネルは 1 つだけであり、ハイライトが計算される場所でマスクのサンプリング値が乗算されます。
Shader "Unlit/009"
{
Properties
{
// 漫反射颜色
_Diffuse ("Diffuse",Color) = (1,1,1,1)
// 高光反射颜色值
_Specular ("Specular",Color) = (1,1,1,1)
// 高光反射值
_Gloss ("_Gloss",range(1,100)) = 5
// 主纹理
_MaxTex ("_MaxTex",2d) = "white" {}
// 高光遮罩纹理
_SpecularMask ("Specular Mask",2d) = "white" {}
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
sampler2D _MaxTex;
float4 _MaxTex_ST;
sampler2D _SpecularMask;
float4 _SpecularMask_ST;
float4 _Specular;
float4 _Diffuse;
float _Gloss;
struct v2f
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
float2 maskUv : TEXCOORD1;
float3 worldNormal : TEXCOORD2;
float3 viewDir : TEXCOORD3;
};
v2f vert (appdata_base v)
{
v2f o;
// 将对象空间中的点变换到齐次坐标中的摄像机裁剪空间
o.vertex = UnityObjectToClipPos(v.vertex);
// 计算uv坐标
o.uv = TRANSFORM_TEX(v.texcoord,_MaxTex);
o.maskUv = TRANSFORM_TEX(v.texcoord,_SpecularMask);
// o.uv = v.texcoord.xy * _MaxTex_ST.xy + _MaxTex_ST.zw;
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.viewDir = normalize(WorldSpaceViewDir(o.vertex));
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float3 texColor = tex2D(_MaxTex,i.uv);
// 半兰伯特漫反射
float halfLambert = (dot(_WorldSpaceLightPos0.xyz,i.worldNormal) * 0.5 + 0.5);
// 采样渐变纹理 使用半兰伯特做uv坐标
float3 diffuse = texColor * _LightColor0.rgb * _Diffuse.rgb * halfLambert;
// 计算高光
float3 halfVector = normalize(normalize(_WorldSpaceLightPos0.xyz) + i.viewDir);
// 采样遮罩纹理
float3 maskColor = tex2D(_SpecularMask,i.maskUv);
// 计算高光
float3 specular = texColor * _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(halfVector,i.worldNormal)),_Gloss) * maskColor;
return float4(diffuse + specular,1);
}
ENDCG
}
}
}
地形マップ: マスクマップ:
効果は次のとおりです。左がマスク テクスチャなし、右がマスク テクスチャありです。
マスクされていないテクスチャの両側にハイライトがあることがわかります。