网站首页 > 技术文章 正文
【Unity Shaders】学习笔记——SurfaceShader(九)Cubemap
-
如果你想从零开始学习Unity Shader,那么你可以看看本系列的文章入门,你只需要稍微有点编程的概念就可以。
-
水平有限,难免有谬误之处,望指出。
上一节中讲述了制作Cubemap的方法。这一节讲讲怎么使用它。
Simple Cubemap
先来看一下最简单的Cubemap。
Shader "Custom/SimpleReflection"
{
Properties
{
_MainTint ("Diffuse Tint", Color) = (1,1,1,1)
_MainTex ("Base (RGB)", 2D) = "white" {}
_Cubemap ("CubeMap", CUBE) = ""{}
_ReflAmount ("Reflection Amount", Range(0.01, 1)) = 0.5
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Lambert
sampler2D _MainTex;
samplerCUBE _Cubemap;
float4 _MainTint;
float _ReflAmount;
struct Input
{
float2 uv_MainTex;
float3 worldRefl;
};
void surf (Input IN, inout SurfaceOutput o)
{
half4 c = tex2D (_MainTex, IN.uv_MainTex) * _MainTint;
o.Emission = texCUBE(_Cubemap, IN.worldRefl).rgb * _ReflAmount;
o.Albedo = c.rgb;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
其实就是用texCUBE函数来对Cubemap采样。
第二个参数应该传入UV坐标,但实际传入的是世界反射向量。这是Unity替我们计算了UV坐标。
Cubemap是由六张贴图构成的,组成了一个类似天空盒的六面体,那这六张贴图是如何映射到小球上的呢,使小球看起来像倒映着周围的环境一样?
将Cubemap想象成一个立方体,包裹着小球,从小球的中心点发射一条射线,穿过小球表面和立方体表面,射线与小球和立方体会各有一个交点,小球上的这个点对应的就是立方体上的这个点的纹理。那要怎样计算各个点对应的UV坐标呢?从小球中心点发射的一条条射线其实就是法线,有个很明显的事实就是,法线朝向法线坐标分量最大的坐标轴指向的立方体的面。也就是坐标(1,1,3)的法线朝向的是Z轴指向的立方体的面。这样就由法线找到了点对应的面。那么UV坐标又该如何计算呢?法线另外两个较小的坐标值和UV坐标是有关系的。举个特殊点的例子,小球最顶点的法线的坐标应该是(0,0,1),对应的应该是立方体的顶面的中点,UV坐标应该是(0.5,0.5)。计算UV坐标的方法是(x/z×0.5+0.5,y/z×0.5+0.5),将特殊点代进去,答案是正确的。原理有点难说明,相当于把X、Y坐标投影到了对应的面上。乘0.5加0.5是为了让法线坐标的区间从[-1,1]变为[0,1]。
在Unity里,我们不必自己计算,可以直接使用内置变量worldRefl来检索Cubemap。
Normal Cubemap
Shader "Custom/NormalMappedReflection"
{
Properties
{
_MainTint ("Diffuse Tint", Color) = (1,1,1,1)
_MainTex ("Base (RGB)", 2D) = "white" {}
_NormalMap ("Normal Map", 2D) = "bump" {}
_Cubemap ("Cubemap", CUBE) = ""{}
_ReflAmount ("Reflection Amount", Range(0,1)) = 0.5
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
CGPROGRAM
#pragma surface surf Lambert
samplerCUBE _Cubemap;
sampler2D _MainTex;
sampler2D _NormalMap;
float4 _MainTint;
float _ReflAmount;
struct Input
{
float2 uv_MainTex;
float2 uv_NormalMap;
float3 worldRefl;
INTERNAL_DATA
};
void surf (Input IN, inout SurfaceOutput o)
{
half4 c = tex2D (_MainTex, IN.uv_MainTex);
float3 normals = UnpackNormal(tex2D(_NormalMap, IN.uv_NormalMap)).rgb;
o.Normal = normals;
o.Emission = texCUBE (_Cubemap, WorldReflectionVector (IN, o.Normal)).rgb * _ReflAmount;
o.Albedo = c.rgb * _MainTint;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}
就只是增加了法线贴图而已。 因为法线信息的改变,所以要重新计算传入texCUBE函数的世界反射向量。float3 worldRefl;INTERNAL_DATA变量用于原本的法线信息不使用时,比如使用了法线贴图,原来的法线信息就不使用了。要根据新的法线信息计算世界反射向量使用WorldReflectionVector函数。
动态立方图系统
有时候游戏里的物体从一个环境走到另一个环境,需要更换Cubemap,这样显示的反射效果才真实。
有两种方法更换Cubemap,一种是实时更换Cubemap,这样的效果最真实,但是要牺牲性能;第二种是当物体走到另一个环境的时候更换Cubemap,这就是我要讲的方法。
方法很简单,就是在C#里用SetTexture的方法动态更换Cubemap。
[ExecuteInEditMode]
public class SwapCubemaps : MonoBehaviour
{
public Cubemap cubeA;
public Cubemap cubeB;
public Transform posA;
public Transform posB;
private Material curMat;
private Cubemap curCube;
// Use this for initialization
void Start
{
}
// Update is called once per frame
void Update
{
curMat = renderer.sharedMaterial;
if(curMat)
{
curCube = CheckProbeDistance;
curMat.SetTexture("_Cubemap", curCube);
}
}
private Cubemap CheckProbeDistance
{
float distA = Vector3.Distance(transform.position, posA.position);
float distB = Vector3.Distance(transform.position, posB.position);
if(distA < distB)
{
return cubeA;
}
else if(distB < distA)
{
return cubeB;
}
else
{
return cubeA;
}
}
void OnDrawGizmos
{
Gizmos.color = Color.green;
if(posA)
{
Gizmos.DrawWireSphere(posA.position, 0.5f);
}
if(posB)
{
Gizmos.DrawWireSphere(posB.position, 0.5f);
}
}
}
-
[ExecuteInEditMode]是为了让脚本在编辑器状态的时候也能执行,这样就不必点击Play调试,比较方便。
-
OnDrawGizmos是在Scene里画一些可视化的东西方便调试。
-
CheckProbeDistance里用Distance判断物体和A点、B点的距离,根据距离决定返回哪种Cubemap。
-
在Update设置材质的Cubemap。
Cubemap效果
Cubemap
我有加个金属的纹理。效果是这样的。
NormalCubemap
这是加了法线贴图的。
猜你喜欢
- 2024-12-04 Unity实例教程:创建渐入的列车
- 2024-12-04 使用Unity为数字人添加逼真的毛发细节
- 2024-12-04 大咖说|Shader知识普及之:图解水波纹效果的实现
- 2024-12-04 纹理烘焙原理及实现【Unity】
- 2024-12-04 unity必备的十大插件(1)
- 2024-12-04 3D游戏模型该如何制作?
- 2024-12-04 Unity学习笔记 - Assets, Objects and Serialization
- 2024-12-04 研发实战:用Unity、3D Max、V-Ray等工具为Quest开发高质量图像
- 2024-12-04 Unity基础 | 70分钟带你轻松入门
- 2024-12-04 「游戏开发」请别再说Unity不如Unreal:Unity室内场景 + 光照练习 3
- 1509℃桌面软件开发新体验!用 Blazor Hybrid 打造简洁高效的视频处理工具
- 534℃Dify工具使用全场景:dify-sandbox沙盒的原理(源码篇·第2期)
- 496℃MySQL service启动脚本浅析(r12笔记第59天)
- 475℃服务器异常重启,导致mysql启动失败,问题解决过程记录
- 473℃启用MySQL查询缓存(mysql8.0查询缓存)
- 453℃「赵强老师」MySQL的闪回(赵强iso是哪个大学毕业的)
- 432℃mysql服务怎么启动和关闭?(mysql服务怎么启动和关闭)
- 430℃MySQL server PID file could not be found!失败
- 最近发表
- 标签列表
-
- c++中::是什么意思 (83)
- 标签用于 (65)
- 主键只能有一个吗 (66)
- c#console.writeline不显示 (75)
- pythoncase语句 (81)
- es6includes (73)
- windowsscripthost (67)
- apt-getinstall-y (86)
- node_modules怎么生成 (76)
- chromepost (65)
- c++int转char (75)
- static函数和普通函数 (76)
- el-date-picker开始日期早于结束日期 (70)
- js判断是否是json字符串 (67)
- checkout-b (67)
- localstorage.removeitem (74)
- vector线程安全吗 (70)
- & (66)
- java (73)
- js数组插入 (83)
- linux删除一个文件夹 (65)
- mac安装java (72)
- eacces (67)
- 查看mysql是否启动 (70)
- 无效的列索引 (74)