Unity Shader编程】之高级纹理

一,立方体纹理 Cubemap 用途

用途 说明
反射贴图 表面镜面高光或金属反射
环境光采样 模拟环境对物体的影响
天空盒背景 使用六张图拼接场景背景
全景投影 做360度相机渲染、投影等

二,创建立方体纹理

在 Unity 中创建和保存一个 立方体纹理(Cubemap) 有几种方式,具体看你是想要:


✅ 一、使用现成的六张图片创建 Cubemap(最常用)

步骤如下:
  1. 准备六张图像
    命名如下(Unity 会自动识别):

    myCubemap_front.png
    myCubemap_back.png
    myCubemap_left.png
    myCubemap_right.png
    myCubemap_up.png
    myCubemap_down.png
    
  2. 导入到 Unity
    将六张图拖入 Unity 的 Assets 目录中。

  3. 创建 Cubemap 资源

    • Assets 空白处右键 > Create > Legacy > Cubemap
    • 命名为 MyCubemap
    • 选中它后,在 Inspector 中点击 各个面,分别拖入上面对应的图(Unity 会有 Front、Back、Left、Right、Up、Down 的插槽)
  4. 保存 Cubemap
    这个 .cubemap 文件本身就会保存在你的项目里,可以直接拖入 Shader 中的 Cubemap 属性上。


✅ 二、将全景图(equirectangular)转换为 Cubemap(HDRI)

Unity 也支持将 全景 HDR 图 转换为 Cubemap 使用,适用于天空盒、环境反射等。

  1. 拖入 .hdr.exr 图像到项目中

  2. 在该图像的 Inspector 里设置:

    • Texture TypeCube
    • MappingLatitude-Longitude Layout
    • Generate Mip Maps → 勾选(反射需要)
    • Apply

这样 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:

  1. GameObject > Light > Reflection Probe
  2. 设置为 Baked 或 Realtime
  3. 勾选 Box Projection(可选)
  4. 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 是纯黑图,你会发现玻璃表面失去了高光反射感,变“哑光”

你可能感兴趣的:(UnityShader,unity,游戏引擎)