效果预览
基本原理
和视差映射类似,但是是把一个正方体内的东西映射到一个平面上。
知识准备
CubeMap采样
如何获取室内点
这里和求AABB的方法相同。
首先假设相机cam,视角方向d,近平面交点为p1远平面焦点为p2设时间t。
那么可以得到这个公式。
①p1=cam+t1*d
②p2=cam+t2*d
由于贴图是模拟窗户,所以p1就是每个frag片元的位置,然后现在需要知道p2的位置。
我们把②反过来看t2就可以得到t2=(p2-cam)/d
看上去这是一个鸡生蛋蛋生鸡的问题,但是这个包围盒的大小我们是可以设置的。例如在模型空间内这个包围盒是(0,0,0)~(1,1,1),但是我们选择在(-1,-1,-1)~(1,1,1)内计算(这里后面会解释)。
那么t2要如何解出来呢,可以这么想:一根射线穿过盒子,可以当做是和盒子的6个平面(这6个平面是无限大的)做相交。
我们先看二维情况,三维是完全相同的。
我们把d沿着x和y做分解,那么就会得到这个射线与x=1这根直线以及与y=1这根直线相交的时间tx,ty那么直线和包围盒相交的点p2的时间t2应该是min(tx,ty),这是因为首先它和x=1相交了。
放到三维则就是与平面x=1,平面y=1,平面z=1相交,同理t2应该是min(tx,ty,tz)。
这时还有一个小问题就是这里是和y=1做相交,但是室内还有y=-1的时候呢,这又怎么解决呢?
这里就是用(-1,-1,-1)~(1,1,1)的好处了,我们只需要对方向取上绝对值即t2=abs(1/d)-cam/d就可以把=-1的问题和=1问题统一了。
此时就可以用得到的t2反解出p2了,p2=p1+t2*d由于我们是在frag里使用,因此p2
就可以直接使用p1来计算,原理和cam一样。
如何把获取到的p2用于cubemap采样
我们先前使用(-1,-1,-1)~(1,1,1)定义p2还有一个好处就是在这,cubemap的范围就是从(-0.5,-0.5,-0.5)~(0.5,0.5,0.5),我们直接使用p2的点坐标就是对应的cubemap的uvw。
代码
Shader "Unlit/fakeInterior"
{
Properties
{
_RoomTex ("Texture", CUBE) = "white" {}
_Rotate("Rotate around XYZ",vector)=(0,0,0)
}
SubShader
{
Tags { "RenderType"="Opaque" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
//旋转函数
float3 rotateVectorAboutX(float angle, float3 vec)
{
angle = radians(angle);
float3x3 rotationMatrix ={float3(1.0,0.0,0.0),
float3(0.0,cos(angle),-sin(angle)),
float3(0.0,sin(angle),cos(angle))};
return mul(vec, rotationMatrix);
}
float3 rotateVectorAboutY(float angle, float3 vec)
{
angle = radians(angle);
float3x3 rotationMatrix ={float3(cos(angle),0.0,sin(angle)),
float3(0.0,1.0,0.0),
float3(-sin(angle),0.0,cos(angle))};
return mul(vec, rotationMatrix);
}
float3 rotateVectorAboutZ(float angle, float3 vec)
{
angle = radians(angle);
float3x3 rotationMatrix ={float3(cos(angle),-sin(angle),0.0),
float3(sin(angle),cos(angle),0.0),
float3(0.0,0.0,1.0)};
return mul(vec, rotationMatrix);
}
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float3 vertex_OS:TEXCOORD1;
float3 viewDir_OS:TEXCOORD2;
float4 vertex : SV_POSITION;
};
samplerCUBE _RoomTex;
float4 _RoomTex_ST;
float4 _Rotate;
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
// slight scaling adjustment to work around "noisy wall"
// when frac() returns a 0 on surface
o.vertex_OS = v.vertex * _RoomTex_ST.xyx * 0.999 + _RoomTex_ST.zwz;
// get object space camera vector
float4 objCam = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos, 1.0));
o.viewDir_OS = v.vertex.xyz - objCam.xyz;
// adjust for tiling
o.viewDir_OS *= _RoomTex_ST.xyx;
return o;
}
fixed4 frag(v2f i):SV_TARGET{
float3 d=normalize(i.viewDir_OS);
float3 pos=frac(i.vertex_OS);
pos=pos*2-1;
float tx=abs(1/d.x)-pos.x/d.x;
float ty=abs(1/d.y)-pos.y/d.y;
float tz=abs(1/d.z)-pos.z/d.z;
float t=min(tx,min(ty,tz));
float3 p2=pos+t*d;
//rotate
p2=rotateVectorAboutX(_Rotate.x,p2);
p2=rotateVectorAboutY(_Rotate.y,p2);
p2=rotateVectorAboutZ(_Rotate.z,p2);
fixed3 col;
col=texCUBE(_RoomTex,p2);
return fixed4(col,1.0);
}
ENDCG
}
}
}
参考
fakeinterior实现:
https://zhuanlan.zhihu.com/p/376762518
https://zhuanlan.zhihu.com/p/159439811
制作cubemap
https://blog.csdn.net/weixin_43839583/article/details/104213119
旋转cubemap
https://polycount.com/discussion/97779/shader-rotate-cubemap-any-tips
Q.E.D.