其他分享
首页 > 其他分享> > 案例学习——Interior Mapping 室内映射(假室内效果)

案例学习——Interior Mapping 室内映射(假室内效果)

作者:互联网

最近油管推荐了Interior Mapping的教程,发现很有意思,发现各种资料都比较零散
于是到处搜集学习了一些资料,有了这篇文章汇总,大家一起学习学习

案例学习——Interior Mapping 室内映射(假室内效果)

1 背景介绍——虚假的窗户

什么是Interior Mapping?我们先从游戏里的窗户开始说起

这是GTA IV的中的一个家具店
在这里插入图片描述
我们仔细看看,可以发现

让房间出现在窗户后面,最简单的方法就是用实际的模型填充每个房间。

这对小规模的场景来说是适用的,但对于大型游戏显然是不切实际的。

这些内部模型三角形面片所产生的消耗,对于大型游戏来说实在太夸张了,尤其是我们在游玩时往往只会偶尔看到少数几个房间的一小部分。

那么,如何来平衡性能和效果呢?答案正如最开始提到的,是Shader上的小Trick

2 怎么模拟窗户?

2.1 通用——视差映射(Parallax Mapping)

Shader是将几何信息作为输入,一顿操作输出颜色,我们唯一关心的只是最终输出颜色在场景中看起来正确,中间发生的事情并不重要。

所以如果我们能偏移输出颜色,使它看起来像输入了几何模型一样偏移,不就达到目的了嘛。

如果输出的偏移不均匀,则可以使渲染的图像看起来好像在某种程度上发生了歪斜,扭曲或变形。

怎么偏移呢?我们会马上想到这方面通用的偏移技术,视差映射(Parallax Mapping)

这里是之前学习LearnOpenGL上视差映射的笔记

在使用视差映射时,我们输入纹理坐标,根据观察者角度和每个像素的“深度”值进行偏移。通过确定相机射线与表面高度相交的点,我们可以创建相当于3D投影出来的2D图像。

视差映射能实现室内效果吗?虽然看起来非常适合我们的需求,也就是在2D平面表示3D效果的需求。

但作为通用解决方案的视差映射,在针对室内场景做的特定情况下使用时,似乎又不太行。

除了迭代次数的消耗大,我们也发现窗户的体积是在内部的,视差通常模拟的是表面的凹凸程度,而不是对于内部的模拟。

当通用解决方案失败时,我们需要考虑可能满足的简单的特定解决方案。

2.2 特定——室内映射(Interior Mapping)

具体问题具体分析,我们要窗户的shader如何进行偏移呢?

在我们的情况下,我们希望将矩形房间插入显示到我们的窗户中。

视差贴图的通用性意味着必须使用迭代数值方法,因为没有分析方法可以确定我们的相机射线在哪里与表面高度相交。

如果我们仅将问题限制在矩形盒子的房间上,那么,只需将房间体积内的相交点映射到纹理,然后输出正确的颜色即可。
在这里插入图片描述
Joost van Dongen在2008年CGI会议上发表了一篇论文:Interior Mapping: A new technique for rendering realistic buildings,作为该技术的起源(这是作者提供的演示demo)。

和之前体绘制shader的思路很像,论文中,Interior Mapping考虑建筑物本身不包含任何额外的几何形状,内部的体积仅是虚假的存在于着色器中。

其把建筑网格的mesh划分为很多“房间”,并对每个房间窗户的纹理像素进行raycast。
使用相机射线与房间box之间的交点处的坐标,来采样一组“房间纹理”。

在这里插入图片描述

于是,类似于视差贴图,它偏移了输入纹理的坐标,给每个隐藏的“房间box”提供墙壁天花板和地板纹理的投影。

在不增加其他几何形状和材质复杂性的情况下,它较好的表示了内部空间。
在这里插入图片描述
其技术广泛运用在如今的游戏里,像下面这些窗户中“以假乱真”的房间

还有很多游戏中的例子,尽管所有这些都表明,它们这些透过窗户看到的房间是伪造的,但Interior Mapping的应用让它们在透视上是完全正确的,并且具有真实的深度。

3 实现方式

3.1 对象空间/切线空间

在论文的实现中,内部的房间是在对象空间或世界空间中定义的。

这确实很容易直接使用,但其在带有倾斜或弯曲墙壁的建筑物上表现不太好

房间受到建筑几何形状的限制,可能导致不平坦或房间截断,就像下图,论文作者的演示demo中的例子
在这里插入图片描述
在现实中,房间几乎总是与建筑物的外部对齐。

所以我们更愿意让所有的房间与mesh对齐,然后向内挤压,向建筑的中心延伸。
在这里插入图片描述
为了做到这一点,我们可以计算寻找一个替代的坐标系,它与我们的表面一致,也就是我们可以到切线空间去做raycast计算。

即使世界空间是弯曲的,但是切线空间永远是轴对齐的。

在切线空间计算后,我们的房间可以随建筑物的曲率而变化,并且始终具有与建筑物外部平行的整面墙。

3.2 房间贴图

对于房间的贴图,论文中要求为墙壁、地板和天花板提供单独的纹理。

这虽然能用,但很难操作。要保持这三种纹理的同步一致,将多个房间的纹理放在一起,是比较困难的。

于是人们也提出了一些其他的方法

3.2.1 立方体贴图——《七大罪》

Zoe J Wood在 “Interior Mapping Meets Escher”中用立方体贴图替代了原本的贴图。

《七大罪》的技术分享中,也使用了这个方式,他们室内用了cubeMap,然后加上一张窗户的贴图,以及提供深度调节景深
在这里插入图片描述
在这里插入图片描述
但立方体贴图也意味着,人们基本不可能人为对贴图进行绘制微调,这对艺术家构建多种的内部贴图资产不太友好。

3.2.2 预投影2D贴图——《模拟城市5》

《模拟城市5》的开发者Andrew Willmott在“From AAA to Indie: Graphics R&D”的演讲中提到,他们在《模拟城市5》中为内部地图使用了预先投影的内部纹理,这是当时的PPT
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
这个方式是比较好的,具有很高的创作性,易于使用,并且展示的结果仅比完整的立方体贴图差一点。

而且它可以在每个建筑物的基础上构建大量的室内图集,随机选择,达到仅使用一个纹理资源,建筑物就可以保持具有随机变化的内部场景风格。

只能说和cubemap的方法各有利弊吧

3.2.3 预投影2D贴图——《极限竞速:地平线4》

同样的,Playground Games的技术美术总监Gareth Harwood在The Gamasutra Deep Dives的一篇访谈中也提到,《极限竞速:地平线4》中也使用了预投影2D贴图来实现室内映射,制作街景的窗户
在这里插入图片描述
访谈提到有几个重要部分:

窗户+窗框+内部形成三层纹理;地图集的使用;夜间与白天的纹理切换;小角度的处理;内部纹理建模的注意事项;规则化摆放内部纹理;性能优化……

下面摘录一些访谈的内容,比较具有参考意义,翻译了一下可以了解了解

关于《极限竞速:地平线4》所用到的高清图,可以去制作者的A站这个链接下载
在这里插入图片描述

4 动手试试

实现上,借用了Unity商城里的免费资源Fake Interiors FREE里面的模型和部分贴图,并参考了其中的shader结构,以及参考了Unity论坛的讨论colin大大的实现

核心的思想就是,如何采样虚拟的房间
因为我们是在窗户(朝着我们的这个面上)运行shader,那么从窗户看进去的一根光线会打中后面虚拟房间box的哪个点呢?
也就是光线与AABB(轴对齐包围盒)的相交问题

先以2D为例子,对于给定的一个光线
我们可以分别求出它与竖直和水平面的交点,也就是分别在X轴和Y轴上求相交
我们取所有进入时间( t m i n t_{min} tmin​)里的max,出去的时间 t m a x t_{max} tmax​里的min,
于是得到了进入和出去包围盒的 t t t 的值
在这里插入图片描述
对于3D盒子的处理也是一样,通用解法如下
假设出发点某个点,为 P P P点,视线的方向向量为 d ⃗ \vec{d} d
以“时间” t t t为自变量,我们就可以得到射线 L ( t ) = P + t d ⃗ L(t)=P+t\vec{d} L(t)=P+td
对它与轴对齐包围盒 ( B m i n , B m a x ) (B_{min},B_{max}) (Bmin​,Bmax​)做相交测试
在这里插入图片描述
在这里插入图片描述
一个包围盒有6个矩形面,把两个互相平行的矩形看成一块板,那么问题就转化为求射线与互相垂直的3块板的相交

以X轴的为例(如图b),我们可以得到在X轴上进入的时间 t 0 x t_0x t0​x和 t 1 x t_1x t1​x
在这里插入图片描述
在这里插入图片描述
Y和Z轴同理,然后在XYZ的 t 0 t_0 t0​里取最大可以得到入点,在XYZ的 t 1 t_1 t1​里取最小就可以得到出点了

在具体应用的时候,根据需要再小作调整,可以看下面代码的实现过程

另外,射线与各种形状的相交算法可以看看这个网页

4.1 立方体贴图

首先实现一个立方体贴图的方法
比如用这么个cubemap
在这里插入图片描述

4.1.1 ObjectSpace的方法

我们先不管切线空间,先从在object空间实现的方法开始,
为避免分散注意,先只展示shader的主干思想

v2f vert(appdata v)
{
	v2f o;
	o.pos = UnityObjectToClipPos(v.vertex);
	// slight scaling adjustment to work around "noisy wall" 
	// when frac() returns a 0 on surface
	o.uvw = v.vertex * _RoomCube_ST.xyx * 0.999 + _RoomCube_ST.zwz;

	// get object space camera vector
	float4 objCam = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos, 1.0));
	o.viewDir = v.vertex.xyz - objCam.xyz;

	// adjust for tiling
	o.viewDir *= _RoomCube_ST.xyx;
	return o;
}
fixed4 frag(v2f i) : SV_Target
{
	// room uvws
	float3 roomUVW = frac(i.uvw);

	// raytrace box from object view dir
	// transform object space uvw( min max corner = (0,0,0) & (+1,+1,+1))  
	// to normalized box space(min max corner = (-1,-1,-1) & (+1,+1,+1))
	float3 pos = roomUVW * 2.0 - 1.0;

	// for axis aligned box Intersection,we need to know the zoom level
	float3 id = 1.0 / i.viewDir;
	
	// k means normalized box space depth hit per x/y/z plane seperated
	// (we dont care about near hit result here, we only want far hit result)
	float3 k = abs(id) - pos * id;
	// kmin = normalized box space real hit ray length
	float kMin = min(min(k.x, k.y), k.z);
	// normalized box Space real hit pos = rayOrigin + kmin * rayDir.
	pos += kMin * i.viewDir;

	// randomly flip & rotate cube map for some variety
	float3 flooredUV = floor(i.uvw);
	float3 r = rand3(flooredUV.x + flooredUV.y + flooredUV.z);
	float2 cubeflip = floor(r.xy * 2.0) * 2.0 - 1.0;
	pos.xz *= cubeflip;
	pos.xz = r.z > 0.5 ? pos.xz : pos.zx;


	// sample room cube map
	fixed4 room = texCUBE(_RoomCube, pos.xyz);
	return fixed4(room.rgb, 1.0);
}

可以发现,能在内部看到box
但是对于ObjectSpace的方法,内部房间只能严格按照轴对齐排列,在曲面上显得很奇怪
在这里插入图片描述

4.1.2 TangentSpace的方法

为了让box在曲面也能表现良好,我们到切线空间中进行求交,代码整体上差不多

v2f vert(appdata v)
{
	v2f o;
	o.pos = UnityObjectToClipPos(v.vertex);
	// uvs
	o.uv = TRANSFORM_TEX(v.uv, _RoomCube);

	// get tangent space camera vector
	float4 objCam = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos, 1.0));
	float3 viewDir = v.vertex.xyz - objCam.xyz;
	float tangentSign = v.tangent.w * unity_WorldTransformParams.w;
	float3 bitangent = cross(v.normal.xyz, v.tangent.xyz) * tangentSign;
	o.viewDir = float3(
		dot(viewDir, v.tangent.xyz),
		dot(viewDir, bitangent),
		dot(viewDir, v.normal)
		);

	// adjust for tiling
	o.viewDir *= _RoomCube_ST.xyx;
	return o;
}
fixed4 frag(v2f i) : SV_Target
{
	// room uvs
	float2 roomUV = frac(i.uv);

	// raytrace box from tangent view dir
	float3 pos = float3(roomUV * 2.0 - 1.0, 1.0);
	float3 id = 1.0 / i.viewDir;
	float3 k = abs(id) - pos * id;
	float kMin = min(min(k.x, k.y), k.z);
	pos += kMin * i.viewDir;

	// randomly flip & rotate cube map for some variety
	float2 flooredUV = floor(i.uv);
	float3 r = rand3(flooredUV.x + 1.0 + flooredUV.y * (flooredUV.x + 1));
	float2 cubeflip = floor(r.xy * 2.0) * 2.0 - 1.0;
	pos.xz *= cubeflip;
	pos.xz = r.z > 0.5 ? pos.xz : pos.zx;
#endif

	// sample room cube map
	fixed4 room = texCUBE(_RoomCube, pos.xyz);
	return fixed4(room.rgb, 1.0);
}

得到效果,在曲面上表现良好
在这里插入图片描述

4.1.3 深度

深度如何实现呢?

我们指定一个深度值,然后在片元着色器前面加上深度映射的代码,我们把深度乘上视线的Z

// Specify depth manually
fixed farFrac = _RoomDepth;

//remap [0,1] to [+inf,0]
//->if input _RoomDepth = 0    -> depthScale = 0      (inf depth room)
//->if input _RoomDepth = 0.5  -> depthScale = 1
//->if input _RoomDepth = 1    -> depthScale = +inf   (0 volume room)
float depthScale = 1.0 / (1.0 - farFrac) - 1.0;
i.viewDir.z *= depthScale;

我们如果在原本的基础上放大了视线Z的倍率,也就是视线向量更加朝着远处走了,采样的时候就好像更深了

在这里插入图片描述

上面的做法是根据z移动的距离来进行在采样上的缩放,会导致四周的贴图失真

也可以用偏移来表达房间的深度,也就是整体前后移动,由此造成能偏移的深度就很有限了,只能是box的深度

可以看这位大大的实现
在这里插入图片描述

4.1.4 代码

整合起来就是这样

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "MyShaders/InteriorMapping_CubeMap"
{
	Properties
	{
		_RoomCube("Room Cube Map", Cube) = "white" {}
		[Toggle(_USEOBJECTSPACE)] _UseObjectSpace("Use Object Space", Float) = 0.0
		_RoomDepth("Room Depth",range(0.001,0.999)) = 0.5
	}
	SubShader
	{
		Tags { "RenderType" = "Opaque" }
		LOD 100

		Pass
		{
			CGPROGRAM
			#pragma vertex vert
			#pragma fragment frag

			#pragma shader_feature _USEOBJECTSPACE

			#include "UnityCG.cginc"

			struct appdata
			{
				float4 vertex : POSITION;
				float2 uv : TEXCOORD0;
				float3 normal : NORMAL;
				float4 tangent : TANGENT;
			};

			struct v2f
			{
				float4 pos : SV_POSITION;
			#ifdef _USEOBJECTSPACE
				float3 uvw : TEXCOORD0;
			#else
				float2 uv : TEXCOORD0;
			#endif
				float3 viewDir : TEXCOORD1;
			};

			samplerCUBE _RoomCube;
			float4 _RoomCube_ST;
			float _RoomDepth;
			// psuedo random 伪随机
			float3 rand3(float co) {
				return frac(sin(co * float3(12.9898,78.233,43.2316)) * 43758.5453);
			}

			v2f vert(appdata v)
			{
				v2f o;
				o.pos = UnityObjectToClipPos(v.vertex);
			#ifdef _USEOBJECTSPACE
				// slight scaling adjustment to work around "noisy wall" when frac() returns a 0 on surface
				o.uvw = v.vertex * _RoomCube_ST.xyx * 0.999 + _RoomCube_ST.zwz;

				// get object space camera vector
				float4 objCam = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos, 1.0));
				o.viewDir = v.vertex.xyz - objCam.xyz;

				// adjust for tiling
				o.viewDir *= _RoomCube_ST.xyx;
			#else
				// uvs
				o.uv = TRANSFORM_TEX(v.uv, _RoomCube);

				// get tangent space camera vector
				float4 objCam = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos, 1.0));
				float3 viewDir = v.vertex.xyz - objCam.xyz;
				float tangentSign = v.tangent.w * unity_WorldTransformParams.w;
				float3 bitangent = cross(v.normal.xyz, v.tangent.xyz) * tangentSign;
				o.viewDir = float3(
					dot(viewDir, v.tangent.xyz),
					dot(viewDir, bitangent),
					dot(viewDir, v.normal)
					);

				// adjust for tiling
				o.viewDir *= _RoomCube_ST.xyx;
			#endif
				return o;
			}

			fixed4 frag(v2f i) : SV_Target
			{

				// Specify depth manually
				fixed farFrac = _RoomDepth;

				//remap [0,1] to [+inf,0]
				//->if input _RoomDepth = 0    -> depthScale = 0      (inf depth room)
				//->if input _RoomDepth = 0.5  -> depthScale = 1
				//->if input _RoomDepth = 1    -> depthScale = +inf   (0 volume room)
				float depthScale = 1.0 / (1.0 - farFrac) - 1.0;
				i.viewDir.z *= depthScale;
			#ifdef _USEOBJECTSPACE
				// room uvws
				float3 roomUVW = frac(i.uvw);

				// raytrace box from object view dir
				// transform object space uvw( min max corner = (0,0,0) & (+1,+1,+1))  
				// to normalized box space(min max corner = (-1,-1,-1) & (+1,+1,+1))
				float3 pos = roomUVW * 2.0 - 1.0;
				
				// for axis aligned box Intersection,we need to know the zoom level
				float3 id = 1.0 / i.viewDir;
				
				// k means normalized box space depth hit per x/y/z plane seperated
				// (we dont care about near hit result here, we only want far hit result)
				float3 k = abs(id) - pos * id;
				// kmin = normalized box space real hit ray length
				float kMin = min(min(k.x, k.y), k.z);
				// normalized box Space real hit pos = rayOrigin + kmin * rayDir.
				pos += kMin * i.viewDir;

				// randomly flip & rotate cube map for some variety
				float3 flooredUV = floor(i.uvw);
				float3 r = rand3(flooredUV.x + flooredUV.y + flooredUV.z);
				float2 cubeflip = floor(r.xy * 2.0) * 2.0 - 1.0;
				pos.xz *= cubeflip;
				pos.xz = r.z > 0.5 ? pos.xz : pos.zx;
			#else
				// room uvs
				float2 roomUV = frac(i.uv);

				// raytrace box from tangent view dir
				float3 pos = float3(roomUV * 2.0 - 1.0, 1.0);
				float3 id = 1.0 / i.viewDir;
				float3 k = abs(id) - pos * id;
				float kMin = min(min(k.x, k.y), k.z);
				pos += kMin * i.viewDir;

				// randomly flip & rotate cube map for some variety
				float2 flooredUV = floor(i.uv);
				float3 r = rand3(flooredUV.x + 1.0 + flooredUV.y * (flooredUV.x + 1));
				float2 cubeflip = floor(r.xy * 2.0) * 2.0 - 1.0;
				pos.xz *= cubeflip;
				pos.xz = r.z > 0.5 ? pos.xz : pos.zx;
			#endif

				// sample room cube map
				fixed4 room = texCUBE(_RoomCube, pos.xyz);
				return fixed4(room.rgb, 1.0);
			}
            ENDCG
        }
    }
}

4.2 预投影2d贴图

对于立方体形状的房间,若后壁的大小为可见瓷砖的1/2

此时如果将它们渲染出来,要使用水平FOV为53.13度从开口向后的摄像机。
在这里插入图片描述
在这里插入图片描述

我们于是可以规定这种情况下的深度单位为标准深度,也就是_RoomDepth = 0.5 → depthScale = 1

比如下面这个图,就是用这种情况下渲染出来的

房间深度可以存储在图集纹理的Alpha通道中(标准深度也就是alpha通道为128)
在这里插入图片描述
也可以做很多图集
在这里插入图片描述
当然也可以不管alpha通道,手动来自己调整深度,这两种指定方式代码会说明

也可以去地平线4制作者的A站链接下载图集
在这里插入图片描述

我们的整体思路不变,先在轴对齐包围盒中求交点,然后我们要把原本要采样cubemap的三维空间的交点,映射到2d图片的UV上,采样预投影2d图片

4.2.1 实现

顶点着色器功能和之前一样,围绕切线空间进行

v2f vert(appdata v)
{
	v2f o;
	o.pos = UnityObjectToClipPos(v.vertex);
	o.uv = TRANSFORM_TEX(v.uv, _RoomTex);

	// get tangent space camera vector
	float4 objCam = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos, 1.0));
	float3 viewDir = v.vertex.xyz - objCam.xyz;
	float tangentSign = v.tangent.w * unity_WorldTransformParams.w;
	float3 bitangent = cross(v.normal.xyz, v.tangent.xyz) * tangentSign;
	o.tangentViewDir = float3(
		dot(viewDir, v.tangent.xyz),
		dot(viewDir, bitangent),
		dot(viewDir, v.normal)
		);
	o.tangentViewDir *= _RoomTex_ST.xyx;
	return o;
}

片元着色器代码如下

// psuedo random
float2 rand2(float co) {
	return frac(sin(co * float2(12.9898,78.233)) * 43758.5453);
}

fixed4 frag(v2f i) : SV_Target
{
	// room uvs
	float2 roomUV = frac(i.uv);
	float2 roomIndexUV = floor(i.uv);

	// randomize the room
	float2 n = floor(rand2(roomIndexUV.x + roomIndexUV.y * (roomIndexUV.x + 1)) * _Rooms.xy);
	//float2 n = floor(_Rooms.xy);
	roomIndexUV += n;

	// get room depth from room atlas alpha
	// fixed farFrac = tex2D(_RoomTex, (roomIndexUV + 0.5) / _Rooms).a;
	
	// Specify depth manually
	fixed farFrac = _RoomDepth;
	
	//remap [0,1] to [+inf,0]
	//->if input _RoomDepth = 0    -> depthScale = 0      (inf depth room)
	//->if input _RoomDepth = 0.5  -> depthScale = 1
	//->if input _RoomDepth = 1    -> depthScale = +inf   (0 volume room)
	float depthScale = 1.0 / (1.0 - farFrac) - 1.0;

	// raytrace box from view dir
	// normalized box space's ray start pos is on trinagle surface, where z = -1
	float3 pos = float3(roomUV * 2 - 1, -1);
	// transform input ray dir from tangent space to normalized box space
	i.tangentViewDir.z *= -depthScale;
	float3 id = 1.0 / i.tangentViewDir;
	float3 k = abs(id) - pos * id;
	float kMin = min(min(k.x, k.y), k.z);
	pos += kMin * i.tangentViewDir;

	// remap from [-1,1] to [0,1] room depth
	float interp = pos.z * 0.5 + 0.5;

	// account for perspective in "room" textures
	// assumes camera with an fov of 53.13 degrees (atan(0.5))
	// visual result = transform nonlinear depth back to linear
	float realZ = saturate(interp) / depthScale + 1;
	interp = 1.0 - (1.0 / realZ);
	interp *= depthScale + 1.0;

	// iterpolate from wall back to near wall
	float2 interiorUV = pos.xy * lerp(1.0, farFrac, interp);
	interiorUV = interiorUV * 0.5 + 0.5;

	// sample room atlas texture
	fixed4 room = tex2D(_RoomTex, (roomIndexUV + interiorUV.xy) / _Rooms);
	return fixed4(room.rgb, 1.0);
}
// remap from [-1,1] to [0,1] room depth
float interp = pos.z * 0.5 + 0.5;
// account for perspective in "room" textures
// assumes camera with an fov of 53.13 degrees (atan(0.5))
// visual result = transform nonlinear depth back to linear
float realZ = saturate(interp) / depthScale + 1;
interp = 1.0 - (1.0 / realZ);
interp *= depthScale + 1.0;

// iterpolate from wall back to near wall
float2 interiorUV = pos.xy * lerp(1.0, farFrac, interp);
interiorUV = interiorUV * 0.5 + 0.5;

因为有参考论坛的代码实现,但不理解,于是画图走了一遍代码,把代码每一步的效果都标了出来,看看它做了什么
(从结果倒回去推了半天,依然不甚明白是怎么得到这样做的方法,只是懂个意境了)
在这里插入图片描述
结果如图
在这里插入图片描述

4.2.2 代码

该部分着色器如下

// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'

Shader "MyShaders/InteriorMapping_2D"
{
	Properties
	{
		_RoomTex("Room Atlas RGB (A - back wall fraction)", 2D) = "white" {}
		_Rooms("Room Atlas Rows&Cols (XY)", Vector) = (1,1,0,0)
		_RoomDepth("Room Depth",range(0.001,0.999)) = 0.5
	}
		SubShader
		{
			Tags { "RenderType" = "Opaque" }
			LOD 100

			Pass
			{
				CGPROGRAM
				#pragma vertex vert
				#pragma fragment frag

				#include "UnityCG.cginc"

				struct appdata
				{
					float4 vertex : POSITION;
					float2 uv : TEXCOORD0;
					float3 normal : NORMAL;
					float4 tangent : TANGENT;
				};

				struct v2f
				{
					float4 pos : SV_POSITION;
					float2 uv : TEXCOORD0;
					float3 tangentViewDir : TEXCOORD1;
				};
				sampler2D _RoomTex;
				float4 _RoomTex_ST;
				float2 _Rooms;
				float _RoomDepth;
				v2f vert(appdata v)
				{
					v2f o;
					o.pos = UnityObjectToClipPos(v.vertex);
					o.uv = TRANSFORM_TEX(v.uv, _RoomTex);

					// get tangent space camera vector
					float4 objCam = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos, 1.0));
					float3 viewDir = v.vertex.xyz - objCam.xyz;
					float tangentSign = v.tangent.w * unity_WorldTransformParams.w;
					float3 bitangent = cross(v.normal.xyz, v.tangent.xyz) * tangentSign;
					o.tangentViewDir = float3(
						dot(viewDir, v.tangent.xyz),
						dot(viewDir, bitangent),
						dot(viewDir, v.normal)
						);
					o.tangentViewDir *= _RoomTex_ST.xyx;
					return o;
				}

				// psuedo random
				float2 rand2(float co) {
					return frac(sin(co * float2(12.9898,78.233)) * 43758.5453);
				}

				fixed4 frag(v2f i) : SV_Target
				{
					// room uvs
					float2 roomUV = frac(i.uv);
					float2 roomIndexUV = floor(i.uv);

					// randomize the room
					float2 n = floor(rand2(roomIndexUV.x + roomIndexUV.y * (roomIndexUV.x + 1)) * _Rooms.xy);
					//float2 n = floor(_Rooms.xy);
					roomIndexUV += n;

					// get room depth from room atlas alpha
					// fixed farFrac = tex2D(_RoomTex, (roomIndexUV + 0.5) / _Rooms).a;

					// Specify depth manually
					fixed farFrac = _RoomDepth;

					//remap [0,1] to [+inf,0]
					//->if input _RoomDepth = 0    -> depthScale = 0      (inf depth room)
					//->if input _RoomDepth = 0.5  -> depthScale = 1
					//->if input _RoomDepth = 1    -> depthScale = +inf   (0 volume room)
					float depthScale = 1.0 / (1.0 - farFrac) - 1.0;

					// raytrace box from view dir
					// normalized box space's ray start pos is on trinagle surface, where z = -1
					float3 pos = float3(roomUV * 2 - 1, -1);
					// transform input ray dir from tangent space to normalized box space
					i.tangentViewDir.z *= -depthScale;
					

					float3 id = 1.0 / i.tangentViewDir;
					float3 k = abs(id) - pos * id;
					float kMin = min(min(k.x, k.y), k.z);
					pos += kMin * i.tangentViewDir;

					// remap from [-1,1] to [0,1] room depth
					float interp = pos.z * 0.5 + 0.5;

					// account for perspective in "room" textures
					// assumes camera with an fov of 53.13 degrees (atan(0.5))
					// visual result = transform nonlinear depth back to linear
					float realZ = saturate(interp) / depthScale + 1;
					interp = 1.0 - (1.0 / realZ);
					interp *= depthScale + 1.0;

					// iterpolate from wall back to near wall
					float2 interiorUV = pos.xy * lerp(1.0, farFrac, interp);
					
					interiorUV = interiorUV * 0.5 + 0.5;

					// sample room atlas texture
					fixed4 room = tex2D(_RoomTex, (roomIndexUV + interiorUV.xy) / _Rooms);
					return room;
				}
			ENDCG
		}
	}
	FallBack "Diffuse"
}

5 丰富效果

基础技术就如上所示,自行拓展一下可以得到一些结果

下图用2d投影图为基底,用alpha值存深度&手动指定深度,在图集中随机生成,以及加上窗框,菲涅尔效应,窗户污垢等等
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
另外也找到一些其他效果,比如这篇文章中,其用窗框的SDF图模拟光照效果

总之还有蛮有意思的一个案例(中间那段根据深度变换UV的代码思路,希望能有大神指教了,想不明白Orz)

标签:1.0,room,室内,Mapping,pos,depthScale,viewDir,Interior,float3
来源: https://blog.csdn.net/weixin_43803133/article/details/117296825