Unity Shader编程】之高级纹理

news2025/6/6 3:10:55

一,立方体纹理 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 是纯黑图,你会发现玻璃表面失去了高光反射感,变“哑光”

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2397939.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

类 Excel 数据填报

类 Excel 填报模式,满足用户 Excel 使用习惯 数据填报,可作为独立的功能模块,用于管理业务流程、汇总采集数据,以及开发各类数据报送系统,因此,对于报表工具而言,其典型场景之一就是利用报表模…

Office文档图片批量导出工具

软件介绍 本文介绍一款专业的Office文档图片批量导出工具。 软件特点 这款软件能够批量导出Word、Excel和PPT中的图片,采用绿色单文件设计,体积小巧仅344KB。 基本操作流程 使用方法十分简单:直接将Word、Excel或PPT文件拖入软件&#xf…

【iOS】ARC 与 Autorelease

ARC 与 Autorelease 文章目录 ARC 与 Autorelease前言何为ARC内存管理考虑方式自己生成的对象,自己持有非自己生成的对象,自己也可以持有不再需要自己持有的对象时释放非自己持有的对象无法释放 ARC的具体实现编译期和运行期ARC做的事情ARC实现: __autoreleasing 与 Autoreleas…

铁电液晶破局 VR/AR:10000PPI 重构元宇宙显示体验

一、VR/AR 沉浸感困境:传统显示技术的天花板在哪? (一)纱窗效应与眩晕感:近眼显示的双重枷锁 当用户戴上 VR 头显,眼前像素网格形成的 “纱窗效应” 瞬间打破沉浸感。传统液晶 500-600PPI 的像素密度&…

竞争加剧,美团的战略升维:反内卷、科技与全球化

5月26日,美团发布2025年第一季度业绩报告,交出了一份兼具韧性与创新性的成绩单。 报告显示,公司一季度总营收866亿元,同比增长18%;核心本地商业收入643亿元,同比增长18%;季度研发投入58亿元&a…

(17)课36:窗口函数的例题:例三登录时间与连续三天登录,例四球员的进球时刻连续进球。

(89)例三登录时间 : 保留代码版本 : CREATE TABLE sql_8( user_id varchar(2), login_date date ); insert into sql_8(user_id,login_date) values(A,2024-09-02),(A,2024-09-03),(A,2024-09-04),(B,2023-11-25),(B,2023-12- 3…

高性能分布式消息队列系统(二)

上一篇博客将C进行实现消息队列的用到的核心技术以及环境配置进行了详细的说明,这一篇博客进行记录消息队列进行实现的核心模块的设计 五、项目的需求分析 5.1、项目框架的概念性理解 5.1.1、消息队列的设计和生产消费者模型的关系 在现代系统架构中,…

华为OD机试真题——天然蓄水库(2025A卷:200分)Java/python/JavaScript/C++/C语言/GO六种最佳实现

2025 A卷 200分 题型 本文涵盖详细的问题分析、解题思路、代码实现、代码详解、测试用例以及综合分析; 并提供Java、python、JavaScript、C++、C语言、GO六种语言的最佳实现方式! 2025华为OD真题目录+全流程解析/备考攻略/经验分享 华为OD机试真题《天然蓄水库》: 目录 题目…

【Harmony OS】数据存储

目录 数据存储概述 首选项数据存储 关系型数据库 数据存储概述 • 数据存储 是为了解决应用数据持久化问题,使得数据能够存储在外存中,达到保存或共享目的。 • 鸿蒙应用数据存储包括 本地数据存储 和 分布式数据存储 。 • 本地数据存储 为应用…

MybatisPlus--核心功能--service接口

Service接口 基本用法 MyBatisPlus同时也提供了service接口,继承后一些基础的增删改查的service代码,也不需要去书写。 接口名为Iservice,而Iservice也继承了IRepository,这里提供的方法跟BaseMapper相比只多不少,整…

uniapp调试,设置默认展示的toolbar内容

uniapp调试,设置默认展示的toolbar内容 设置pages.json中 pages数组中 json的顺序就可以只需要调整顺序,不会影响该bar在页面中的显示默认展示第一条page

笔记本电脑开机无线网卡自动禁用问题

1.问题环境 电脑品牌:华硕笔记本天选4 电脑型号:FX507VV 电脑系统:windows 11_x64_24h2 文档编写时间:2025年6月 2.问题现象 1. 笔记本电脑开机之后自动禁用无线网卡 使用USB转RJ45转接头同样无效,这个网卡也给禁…

推荐一款使用html开发桌面应用的工具——mixone

简介 mixone是开发桌面应用(Win、Mac、Linux)的一款工具、其基于electron实现。其拥有简单的工程结构。以为熟悉前端开发的程序员可以很轻松的开发出桌面应用,它比electron的其他框架更简单,因为那些框架基本上还需要了解electro…

【云原生开发】如何通过client-go来操作K8S集群

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,…

八.MySQL复合查询

一.基本查询回顾 分组统计 group by 函数作用示例语句说明count(*)统计记录条数select deptno, count(*) from emp group by deptno;每个部门有多少人?sum(sal)某字段求和select deptno, sum(sal) from emp group by deptno;每个部门总工资avg(sal)求平均值select…

FastMCP vs MCP:协议标准与实现框架的协同

你好,我是 shengjk1,多年大厂经验,努力构建 通俗易懂的、好玩的编程语言教程。 欢迎关注!你会有如下收益: 了解大厂经验拥有和大厂相匹配的技术等 希望看什么,评论或者私信告诉我! 文章目录 一…

AI视频“入驻”手机,多模态成智能终端的新战场

文|乐乐 今天,无线蓝牙耳机(TWS)已经成为人人都用得起的产品。 但退回到9年前,苹果AirPods是全球第一款真正意义上的无线蓝牙耳机。靠着自研并申请专利的Snoop监听技术,苹果解决了蓝牙耳机左右延时和能耗…

nginx+tomcat负载均衡群集

一 案例部署Tomcat 目录 一 案例部署Tomcat 1.案例概述 1.1案例前置知识点 (1)Tomcat简介 (2)应用场景 2.实施准备 (1)关闭Linux防火墙 (2)安装Java 2.1 安装配置TOMACT …

建造者模式:优雅构建复杂对象

引言 在软件开发中,有时我们需要创建一个由多个部分组成的复杂对象,这些部分可能有不同的变体或配置。如果直接在一个构造函数中设置所有参数,代码会变得难以阅读和维护。当对象构建过程复杂,且需要多个步骤时,我们可…

现场总线结构在楼宇自控系统中的技术要求与实施要点分析

在建筑智能化程度不断提升的当下,楼宇自控系统承担着协调建筑内各类设备高效运行的重任。传统的集中式控制系统在面对复杂建筑环境时,逐渐暴露出布线繁琐、扩展性差、可靠性低等问题。而现场总线结构凭借其分散控制、通信高效等特性,成为楼宇…