一,立方体纹理 Cubemap 用途
用途 | 说明 |
---|---|
反射贴图 | 表面镜面高光或金属反射 |
环境光采样 | 模拟环境对物体的影响 |
天空盒背景 | 使用六张图拼接场景背景 |
全景投影 | 做360度相机渲染、投影等 |
二,创建立方体纹理
在 Unity 中创建和保存一个 立方体纹理(Cubemap) 有几种方式,具体看你是想要:
✅ 一、使用现成的六张图片创建 Cubemap(最常用)
步骤如下:
-
准备六张图像:
命名如下(Unity 会自动识别):myCubemap_front.png myCubemap_back.png myCubemap_left.png myCubemap_right.png myCubemap_up.png myCubemap_down.png
-
导入到 Unity:
将六张图拖入 Unity 的Assets
目录中。 -
创建 Cubemap 资源:
- 在
Assets
空白处右键 >Create > Legacy > Cubemap
- 命名为
MyCubemap
- 选中它后,在 Inspector 中点击
各个面
,分别拖入上面对应的图(Unity 会有 Front、Back、Left、Right、Up、Down 的插槽)
- 在
-
保存 Cubemap:
这个.cubemap
文件本身就会保存在你的项目里,可以直接拖入 Shader 中的 Cubemap 属性上。
✅ 二、将全景图(equirectangular)转换为 Cubemap(HDRI)
Unity 也支持将 全景 HDR 图 转换为 Cubemap 使用,适用于天空盒、环境反射等。
-
拖入
.hdr
或.exr
图像到项目中 -
在该图像的
Inspector
里设置:- Texture Type →
Cube
- Mapping →
Latitude-Longitude Layout
- Generate Mip Maps → 勾选(反射需要)
- Apply
- Texture Type →
这样 Unity 就会自动将它作为 Cubemap 处理。
✅ 三、运行时生成立方体纹理(程序生成)
如果你需要运行时创建一个 Cubemap 并保存(例如截图天空),可以这样做:
public class CubemapCapture : MonoBehaviour
{
public Camera renderCamera;
public int cubemapSize = 128;
public string savePath = "Assets/CapturedCubemap.cubemap";
void Start()
{
Cubemap cube = new Cubemap(cubemapSize, TextureFormat.RGBA32, false);
renderCamera.RenderToCubemap(cube);
// 保存
#if UNITY_EDITOR
UnityEditor.AssetDatabase.CreateAsset(cube, savePath);
UnityEditor.AssetDatabase.SaveAssets();
#endif
}
}
⚠️ 注意事项:
- 你必须挂载一个摄像机并指向你想捕捉的中心。
- 只能在 Editor 下使用保存功能(运行时不能创建
.cubemap
文件,除非导出为贴图数组等特殊形式)。
✅ 四、通过 Reflection Probe 生成 Cubemap
Unity 中的 Reflection Probe 本质上也在实时生成 Cubemap:
- GameObject > Light > Reflection Probe
- 设置为 Baked 或 Realtime
- 勾选
Box Projection
(可选) - Bake 后它会自动生成一个 Cubemap,用于环境反射
你可以通过 Shader 中 _ReflectionProbe0
获取它,或绑定到材质里。
其中,Reflection Probe bake的时候,你可能会发现它只bake进去天空盒,那是因为Reflection Probe只会bake进去场景中为static的物体
✅ 小结:创建 Cubemap 的常用方法
方式 | 适用场景 | 是否支持保存 |
---|---|---|
手动导入六图拼接 | 天空盒、自定义 Cubemap | ✔ |
HDRI 贴图转 Cube | 天空盒、环境光 | ✔ |
Camera.RenderToCubemap | 动态反射、截图环境 | ✔(仅 Editor) |
Reflection Probe | 物理反射、全局光照 | 自动生成 |
三,反射,折射贴图
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unity Shaders Book/Chapter 10/Refraction" {
Properties {
// 材质颜色,用于混合漫反射颜色
_Color ("Color Tint", Color) = (1, 1, 1, 1)
// 折射颜色,可用于调节 Cubemap 显示色调
_RefractColor ("Refraction Color", Color) = (1, 1, 1, 1)
// 折射混合权重:0表示完全漫反射,1表示完全折射
_RefractAmount ("Refraction Amount", Range(0, 1)) = 1
// 折射率比(η),用于控制折射方向,通常设置为空气到玻璃的折射率比,如 1.0/1.33
_RefractRatio ("Refraction Ratio", Range(0.1, 1)) = 0.5
// 用于折射的 Cubemap 环境贴图,通常使用场景天空盒
_Cubemap ("Refraction Cubemap", Cube) = "_Skybox" {}
}
SubShader {
// 使用 Opaque 渲染队列,并归类为 Geometry 类型
Tags { "RenderType"="Opaque" "Queue"="Geometry" }
Pass {
// 使用前向渲染的主光源通道
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
// 开启前向光照编译宏(支持多个光源和阴影)
#pragma multi_compile_fwdbase
// 指定使用的顶点/片元函数
#pragma vertex vert
#pragma fragment frag
// 引入 Unity 的光照与阴影宏定义
#include "Lighting.cginc"
#include "AutoLight.cginc"
// 声明从 Properties 中传入的变量
fixed4 _Color;
fixed4 _RefractColor;
float _RefractAmount;
fixed _RefractRatio;
samplerCUBE _Cubemap;
// 顶点输入结构体:包含位置和法线
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
};
// 顶点到片元的传输结构体(插值器)
struct v2f {
float4 pos : SV_POSITION; // 裁剪空间顶点位置(用于屏幕映射)
float3 worldPos : TEXCOORD0; // 顶点世界坐标
fixed3 worldNormal : TEXCOORD1; // 世界法线
fixed3 worldViewDir : TEXCOORD2; // 世界视线方向(摄像机指向像素点)
fixed3 worldRefr : TEXCOORD3; // 世界折射方向
SHADOW_COORDS(4) // Unity 内置阴影坐标宏
};
// 顶点着色器:计算空间变换、折射方向等
v2f vert(a2v v) {
v2f o;
// 将顶点从本地空间转换为裁剪空间,作为 SV_POSITION 输出
o.pos = UnityObjectToClipPos(v.vertex);
// 将法线从本地空间转换为世界空间,并归一化
o.worldNormal = UnityObjectToWorldNormal(v.normal);
// 将顶点位置从对象空间转换到世界空间
o.worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
// 计算视线方向:摄像机 → 当前像素(世界空间)
o.worldViewDir = UnityWorldSpaceViewDir(o.worldPos);
//反射,模拟镜面效果,和折射相冲突,保留一个
// o.worldRefr=reflect(-o.worldViewDir,o.worldNormal);
// 使用 HLSL 内置函数 refract 计算折射方向
// 输入方向 I = -视线方向(从像素指向相机),法线 N,折射率 eta
o.worldRefr = refract(-normalize(o.worldViewDir), normalize(o.worldNormal), _RefractRatio);
// 阴影贴图相关数据插值初始化(支持阴影衰减)
TRANSFER_SHADOW(o);
return o;
}
// 片元着色器:执行漫反射、折射采样、颜色混合等
fixed4 frag(v2f i) : SV_Target {
// 标准化输入的法线
fixed3 worldNormal = normalize(i.worldNormal);
// 获取主光源方向(世界空间)
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
// 获取标准化视线方向(摄像机 → 当前像素)
fixed3 worldViewDir = normalize(i.worldViewDir);
// 获取环境光颜色
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
// 标准 Lambert 漫反射计算
fixed3 diffuse = _LightColor0.rgb * _Color.rgb * max(0, dot(worldNormal, worldLightDir));
// 用折射方向采样 Cubemap 环境贴图,并乘上折射色调
fixed3 refraction = texCUBE(_Cubemap, i.worldRefr).rgb * _RefractColor.rgb;
// 计算阴影衰减(基于阴影贴图)
UNITY_LIGHT_ATTENUATION(atten, i, i.worldPos);
// 最终颜色混合:将漫反射与折射按权重混合
fixed3 color = ambient + lerp(diffuse, refraction, _RefractAmount) * atten;
// 返回最终颜色,alpha = 1(不透明)
return fixed4(color, 1.0);
}
ENDCG
}
}
// 降级 Shader,如果目标平台不支持此 Shader 则使用
FallBack "Reflective/VertexLit"
}
四,渲染纹理
在unity中有一种特殊的pass,GrabPass,定义后,unity会把当前屏幕的图像绘制在一张纹理中,然后我们可以通过对这个纹理来进行操作
4.1 玻璃效果
// Upgrade NOTE: replaced ‘_Object2World’ with ‘unity_ObjectToWorld’
// Upgrade NOTE: replaced ‘mul(UNITY_MATRIX_MVP,)’ with 'UnityObjectToClipPos()’
Shader “Unity Shaders Book/Chapter 10/Glass Refraction” {
Properties {
_MainTex (“Main Tex”, 2D) = “white” {} // 主纹理贴图
_BumpMap (“Normal Map”, 2D) = “bump” {} // 法线贴图
_Cubemap (“Environment Cubemap”, Cube) = “_Skybox” {} // 环境反射贴图(立方体)
_Distortion (“Distortion”, Range(0, 100)) = 10 // 法线扰动强度(影响折射扭曲)
_RefractAmount (“Refract Amount”, Range(0.0, 1.0)) = 1.0 // 折射/反射混合比例
}
SubShader {
Tags { "Queue"="Transparent" "RenderType"="Opaque" }
// 捕捉当前屏幕图像到 _RefractionTex(背景图像用于模拟折射)
GrabPass { "_RefractionTex" }
Pass {
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
samplerCUBE _Cubemap;
float _Distortion;
fixed _RefractAmount;
sampler2D _RefractionTex; // 被 GrabPass 捕捉到的背景图像
float4 _RefractionTex_TexelSize; // 屏幕图像的像素大小(用于精确偏移)
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
float4 tangent : TANGENT;
float2 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float4 scrPos : TEXCOORD0; // 屏幕坐标,用于 GrabPass UV 采样
float4 uv : TEXCOORD1; // uv.xy = 主纹理UV,uv.zw = 法线纹理UV
float4 TtoW0 : TEXCOORD2; // TBN矩阵第1行 + worldPos.x
float4 TtoW1 : TEXCOORD3; // TBN矩阵第2行 + worldPos.y
float4 TtoW2 : TEXCOORD4; // TBN矩阵第3行 + worldPos.z
};
v2f vert (a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); // 模型 → 裁剪空间
// 计算当前像素在屏幕上的 GrabPass 采样位置(自动透视修正)
o.scrPos = ComputeGrabScreenPos(o.pos);
// 主纹理、法线纹理 UV 变换
o.uv.xy = TRANSFORM_TEX(v.texcoord, _MainTex);
o.uv.zw = TRANSFORM_TEX(v.texcoord, _BumpMap);
// 构建 TBN 矩阵(切线空间 → 世界空间)
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
float3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
float3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
// 每行存 TBN basis 的 xyz,w 分量存 worldPos 对应轴
o.TtoW0 = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
o.TtoW1 = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
o.TtoW2 = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
return o;
}
fixed4 frag (v2f i) : SV_Target {
// 恢复世界坐标(从每个 w 分量提取)
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
// 计算从像素指向摄像机的视线方向(世界空间)
fixed3 worldViewDir = normalize(UnityWorldSpaceViewDir(worldPos));
// 从法线贴图采样切线空间法线(范围[-1, 1])
fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw));
// ✅【产生玻璃折射扭曲感】的核心代码:
float2 offset = bump.xy * _Distortion * _RefractionTex_TexelSize.xy;
i.scrPos.xy = offset * i.scrPos.z + i.scrPos.xy;
// 采样 GrabPass 纹理作为“背景折射”颜色
fixed3 refrCol = tex2D(_RefractionTex, i.scrPos.xy / i.scrPos.w).rgb;
// 法线从切线空间转换到世界空间(TBN乘切线法线)
bump = normalize(half3(
dot(i.TtoW0.xyz, bump),
dot(i.TtoW1.xyz, bump),
dot(i.TtoW2.xyz, bump)));
// 计算反射方向
fixed3 reflDir = reflect(-worldViewDir, bump);
// 从 Cubemap 中采样反射环境颜色
fixed4 texColor = tex2D(_MainTex, i.uv.xy);
fixed3 reflCol = texCUBE(_Cubemap, reflDir).rgb * texColor.rgb;
// ✅【反射 + 折射】混合(_RefractAmount 控制)
fixed3 finalColor = reflCol * (1 - _RefractAmount) + refrCol * _RefractAmount;
return fixed4(finalColor, 1); // 不透明(你可以改为支持透明混合)
}
ENDCG
}
}
Fallback "Diffuse"
}
bump.xy 决定了你往哪个方向“挤压/偏移”
_Distortion 放大效果
texel size 保证单位一致(从像素偏移变成 UV 偏移)
i.scrPos.xy 原本就是当前像素在屏幕中的 UV 坐标(GrabPass用)
i.scrPos.z 透视深度校正因子(从 ComputeGrabScreenPos() 中得)
offset * i.scrPos.z 根据深度远近决定偏移强度,防止透视拉伸过度
- i.scrPos.xy 添加偏移量,实现“错位采样”
这是一个折射的功能shader,为什么要添加cubemap来作为反射呢?
➤ 它是模拟玻璃表面上的环境反射(如天空、高光、远处光源反光)
不影响背景的错位采样
不影响整体透视扭曲
仅仅为表面提供一种“微妙的光泽感”
🧪 如果 Cubemap 是纯黑图,你会发现玻璃表面失去了高光反射感,变“哑光”