Textures Cubiques (Cubemaps)
Une texture cubique est composée de six images distinctes qui correspondent aux six faces d'un cube. Cette structure est principalement utilisée pour implémenter le mapping d'environnement, permettant de capturer et de projeetr l'environnement surrounding sur des objets 3D.
Skybox
Pour configurer correctement une skybox avec une texture cubique :
- Utilisez le shader
Skybox/6 Sidedqui nécessite six textures séparées. - Définissez le mode d'enveloppement (Wrap Mode) de chaque texture sur
Clamppour éviter les artefacts visuels aux coutures. - Assurez-vous que le composant Camera de la scène a son paramètre
Clear Flagsréglé surSkybox.
Réflexion
La réflexion environnementale utilise le vecteur de réflexion pour échantillonner la Cubemap. Voici une implémentation optimisée :
Shader "Custom/EnvironmentReflection" {
Properties {
_BaseColor ("Teinte de Base", Color) = (1, 1, 1, 1)
_EnvTint ("Teinte Environnement", Color) = (1, 1, 1, 1)
_ReflectBlend ("Intensité Réflexion", Range(0, 1)) = 1
_EnvMap ("Carte Environnement", Cube) = "_Skybox" {}
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry" }
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex ComputeVertex
#pragma fragment ComputeFragment
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _BaseColor;
fixed4 _EnvTint;
fixed _ReflectBlend;
samplerCUBE _EnvMap;
struct VertexInput {
float4 position : POSITION;
float3 normal : NORMAL;
};
struct VertexOutput {
float4 clipPos : SV_POSITION;
float3 worldPos : TEXCOORD0;
fixed3 worldNormal : TEXCOORD1;
fixed3 viewDir : TEXCOORD2;
fixed3 reflectDir : TEXCOORD3;
SHADOW_COORDS(4)
};
VertexOutput ComputeVertex(VertexInput v) {
VertexOutput o;
o.clipPos = UnityObjectToClipPos(v.position);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(unity_ObjectToWorld, v.position).xyz;
o.viewDir = UnityWorldSpaceViewDir(o.worldPos);
o.reflectDir = reflect(-normalize(o.viewDir), normalize(o.worldNormal));
TRANSFER_SHADOW(o);
return o;
}
fixed4 ComputeFragment(VertexOutput i) : SV_Target {
fixed3 norm = normalize(i.worldNormal);
fixed3 lightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _BaseColor.rgb * saturate(dot(norm, lightDir));
fixed3 envSample = texCUBE(_EnvMap, i.reflectDir).rgb * _EnvTint.rgb;
UNITY_LIGHT_ATTENUATION(shadowAtten, i, i.worldPos);
fixed3 finalLighting = ambient + lerp(diffuse, envSample, _ReflectBlend) * shadowAtten;
return fixed4(finalLighting, 1.0);
}
ENDCG
}
}
FallBack "Reflective/VertexLit"
}
Réfraction
La réfraction suit la loi de Snell-Descartes :
n₁ sin(θ₁) = n₂ sin(θ₂)
Où n₁ et n₂ représentent les indices de réfraction des deux milieux. La fonction refract prend en paramètres le vecteur incident normalisé, la normale de surface normalisée, et le ratio n₁/n₂.
Shader "Custom/EnvironmentRefraction" {
Properties {
_Albedo ("Couleur Albedo", Color) = (1, 1, 1, 1)
_TransmitTint ("Teinte Transmission", Color) = (1, 1, 1, 1)
_RefractBlend ("Intensité Réfraction", Range(0, 1)) = 1
_IOR ("Ratio IOR", Range(0.1, 1)) = 0.5
_EnvTexture ("Texture Cubique", Cube) = "_Skybox" {}
}
SubShader {
Tags { "RenderType"="Opaque" "Queue"="Geometry" }
Pass {
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma multi_compile_fwdbase
#pragma vertex vertShader
#pragma fragment fragShader
#include "Lighting.cginc"
#include "AutoLight.cginc"
fixed4 _Albedo;
fixed4 _TransmitTint;
float _RefractBlend;
fixed _IOR;
samplerCUBE _EnvTexture;
struct appdata {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
struct v2f {
float4 pos : SV_POSITION;
float3 worldPos : TEXCOORD0;
fixed3 worldNormal : TEXCOORD1;
fixed3 worldView : TEXCOORD2;
fixed3 worldRefract : TEXCOORD3;
SHADOW_COORDS(4)
};
v2f vertShader(appdata v) {
v2f 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.worldRefract = refract(-normalize(o.worldView), normalize(o.worldNormal), _IOR);
TRANSFER_SHADOW(o);
return o;
}
fixed4 fragShader(v2f i) : SV_Target {
fixed3 n = normalize(i.worldNormal);
fixed3 l = normalize(UnityWorldSpaceLightDir(i.worldPos));
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
fixed3 diffuse = _LightColor0.rgb * _Albedo.rgb * saturate(dot(n, l));
fixed3 refractSample = texCUBE(_EnvTexture, i.worldRefract).rgb * _TransmitTint.rgb;
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
fixed3 result = ambient + lerp(diffuse, refractSample, _RefractBlend) * atten;
return fixed4(result, 1.0);
}
ENDCG
}
}
FallBack "Reflective/VertexLit"
}
Réflexion de Fresnel
L'effet Fresnel décrit comment la proportion de lumière réfléchie par rapport à la lumière réfractée varie en fonction de l'angle d'observation. L'approximation de Schlick est couramment utilisée en temps réel :
F_schlick(v, n) = F₀ + (1 - F₀) * (1 - v · n)⁵
Textures de Rendu (Render Textures)
Les Render Target Textures (RTT) permettent au GPU de restituer une scène 3D entière dans un tampon intermédiaire. Les Multiple Render Targets (MRT) étendent cette capacité en permettant l'écriture simultanée dans plusieurs textures cibles lors d'une seule passe de rendu, optimisant ainsi les performances.
Voici un shader basique pour échantillonner une texture de rendu :
``` Shader "Custom/RenderTextureSampler" { Properties { _CaptureTex ("Texture Capturée", 2D) = "white" {} } SubShader { Tags { "RenderType"="Opaque" "Queue"="Geometry" } Pass { CGPROGRAM #pragma vertex vertPass #pragma fragment fragPass #include "UnityCG.cginc"
sampler2D _CaptureTex;
float4 _CaptureTex_ST;
struct inputVertex {
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct outputVertex {
float2 uvCoord : TEXCOORD0;
float4 clipPosition : SV_POSITION;
};
outputVertex vertPass(inputVertex v) {
outputVertex o;
o.clipPosition = UnityObjectToClipPos(v.vertex);
o.uvCoord = TRANSFORM_TEX(v.uv, _CaptureTex);
return o;
}
fixed4 fragPass(outputVertex i) : SV_Target {
return tex2D(_CaptureTex, i.uvCoord);
}
ENDCG
}
}
}
GrabPass
--------
Le `GrabPass` est une passe spéciale utilisée pour capturer l'image de l'écran avant le rendu de l'objet. Il est essentiel pour créer des matériaux transparents cmoplexes comme le verre, permettant de déformer l'arrière-plan plutôt que de simplement mélanger les couleurs.
Il existe deux approches principales :
- **GrabPass {}** : Capture l'écran pour chaque objet utilisant cette passe. Très coûteux en performance si plusieurs objets l'utilisent, mais garantit que chaque objet capture l'état exact du tampon d'écran au moment de son rendu.
- **GrabPass {"TextureName"}** : Capture l'écran une seule fois par image pour le premier objet utilisant ce nom de texture. Beaucoup plus performant pour les scènes contenant de multiples objets transparents.
```
Shader "Custom/AdvancedGlass" {
Properties {
_GlassTex ("Texture Verre", 2D) = "white" {}
_NormalTex ("Carte Normale", 2D) = "bump" {}
_ReflectionMap ("Cubemap Réflexion", Cube) = "_Skybox" {}
_RefractPower ("Puissance Distorsion", Range(0, 100)) = 10
_ReflectRatio ("Ratio Réflexion", Range(0.0, 1.0)) = 1.0
}
SubShader {
Tags { "Queue"="Transparent" "RenderType"="Opaque" }
GrabPass { "_ScreenCapture" }
Pass {
CGPROGRAM
#pragma vertex glassVert
#pragma fragment glassFrag
#include "UnityCG.cginc"
sampler2D _GlassTex;
float4 _GlassTex_ST;
sampler2D _NormalTex;
float4 _NormalTex_ST;
samplerCUBE _ReflectionMap;
float _RefractPower;
fixed _ReflectRatio;
sampler2D _ScreenCapture;
float4 _ScreenCapture_TexelSize;
struct appdata {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 uv : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float4 screenPos : TEXCOORD0;
float4 uvMain : TEXCOORD1;
float4 t2w0 : TEXCOORD2;
float4 t2w1 : TEXCOORD3;
float4 t2w2 : TEXCOORD4;
};
v2f glassVert(appdata v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.screenPos = ComputeGrabScreenPos(o.pos);
o.uvMain.xy = TRANSFORM_TEX(v.uv, _GlassTex);
o.uvMain.zw = TRANSFORM_TEX(v.uv, _NormalTex);
float3 wPos = mul(unity_ObjectToWorld, v.vertex).xyz;
fixed3 wNorm = UnityObjectToWorldNormal(v.normal);
fixed3 wTang = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 wBinorm = cross(wNorm, wTang) * v.tangent.w;
o.t2w0 = float4(wTang.x, wBinorm.x, wNorm.x, wPos.x);
o.t2w1 = float4(wTang.y, wBinorm.y, wNorm.y, wPos.y);
o.t2w2 = float4(wTang.z, wBinorm.z, wNorm.z, wPos.z);
return o;
}
fixed4 glassFrag(v2f i) : SV_Target {
float3 wPos = float3(i.t2w0.w, i.t2w1.w, i.t2w2.w);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(wPos));
fixed3 normalTangent = UnpackNormal(tex2D(_NormalTex, i.uvMain.zw));
float2 screenOffset = normalTangent.xy * _RefractPower * _ScreenCapture_TexelSize.xy;
i.screenPos.xy += screenOffset * i.screenPos.z;
fixed3 refractColor = tex2Dproj(_ScreenCapture, i.screenPos).rgb;
fixed3 normalWorld = normalize(half3(
dot(i.t2w0.xyz, normalTangent),
dot(i.t2w1.xyz, normalTangent),
dot(i.t2w2.xyz, normalTangent)
));
fixed3 reflDir = reflect(-viewDir, normalWorld);
fixed4 glassColor = tex2D(_GlassTex, i.uvMain.xy);
fixed3 reflectColor = texCUBE(_ReflectionMap, reflDir).rgb * glassColor.rgb;
fixed3 outputColor = reflectColor * (1 - _ReflectRatio) + refractColor * _ReflectRatio;
return fixed4(outputColor, 1);
}
ENDCG
}
}
FallBack "Diffuse"
}