IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 游戏开发 -> Unity Shader Interior mapping 内部映射 -> 正文阅读

[游戏开发]Unity Shader Interior mapping 内部映射

简述

之前有看到过一种用CubeMap构建出空间的效果,只是一直不知道叫什么名字。最近闲下来了想起了这玩意,就通过万能的谷歌搜到了这个技术的名字——Interior mapping,百度翻译是内部映射。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

然后我又发现已经有大佬写的比较详细了,比如案例学习——Interior Mapping 室内映射(假室内效果)、以及一种假室内(Fake Interior)效果的实现。虽有珠玉在前,但是我还是想按照我自己的思路来记录一下。我找到了这个技术的论文地址,看了一下发现有点头大,因为它上面都是纯原理的东西,没有代码。于是我又翻到一篇比较按照论文的思路来写的代码Interior mapping,总算是解决了我的诸多不解和疑惑。

计算平面

在这里插入图片描述
根据图示我们需要找到pixel上下两个平面(这里用Y轴方向做示例),其中pixel的位置是已知的平面上的点,d为两个平面间距离也是已知的。根据pixeld,我们可以求出上下两个平面的高度,分别是 R o o f = c e i l ( y / d ) ? d Roof=ceil\left ( y/d\right )\ast d Roof=ceil(y/d)?d F l o o r = ( c e i l ( y / d ) ? 1 ) ? d Floor=\left ( ceil\left ( y/d\right )-1\right )\ast d Floor=(ceil(y/d)?1)?d,其中 y y ypixely坐标, c e i l ( ) ceil\left ( \right ) ceil()会返回大于或等于输入值的最小整数。剩下的X轴方向和Z轴方向也是一个意思。
通过上面的计算我们获得了平面的高度,即Y轴方向平面的位置,接下来就可以求交了。接下来就是求直线和平面相交的问题,因为平面是无限大的所以只要直线不平行于平面那相交是必然的(现实的计算里基本上相交是必然的)。那么射线和平面相交该怎么算呢?这篇文章里面有详细的介绍A Minimal Ray-Tracer: Rendering Simple Shapes这里就不展开说了,直接用结论。

在这里插入图片描述
t = ( l 0 ? p o ) ? n l ? n t=\frac{\left ( l_{0}-p_{o}\right )\cdot n}{l\cdot n} t=l?n(l0??po?)?n?
p点即为交点,其中t l 0 l_{0} l0?p点的距离, l 0 l_{0} l0?为射线起点即ro p o p_{o} po?为平面坐标,n为平面法线。转换成代码(Y轴方向):

	float3 rd=normalize(i.viewDir);
	float3 ro=i.objectPos;
	float3 dir=float3()

	float3 wallPos=ceil(ro.y/_Distance)*_Distance*dir;
	t0=dot(wallPos-ro.xyz,dir)/dot(rd,dir);

每个平面都会相交,只要找到最近的那个平面就行了,即t最小的结果,整合调整后的完整代码如下:

Shader "MyShader/InteriorMappingTest"
{
    Properties
    {
        _MainTex ("Texture", 2D) = "white" {}
        _Distance("Distance",Float)=0.2
        _WallColorA("Wall Color A",Color)=(1,0,1,1)
        _WallColorB("Wall Color B",Color)=(1,1,0,1)
        _RoofColor("Roof Color",Color)=(1,0,0,1)
        _FloorColor("Floor Color",Color)=(0,1,1,1)
    }
    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;
            };
            
            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 viewDir:TEXCOORD1;
                float3 objectPos:TEXCOORD2;
            };
            
            sampler2D _MainTex;
            float4 _MainTex_ST;
            float _Distance;

            half3 _WallColorA;
            half3 _WallColorB;
            half3 _RoofColor;
            half3 _FloorColor;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = TRANSFORM_TEX(v.uv, _MainTex);
                float3 worldPos=mul(unity_ObjectToWorld,v.vertex).xyz;
                float3 worldViewDir=UnityWorldSpaceViewDir(worldPos);
                //因为是向量所以w为0
                o.viewDir=-mul(unity_WorldToObject,float4(worldViewDir,0));
                o.objectPos=v.vertex.xyz;

                return o;
            }

            static float3 up=float3(0,1,0);
            static float3 right=float3(1,0,0);
            static float3 forward=float3(0,0,1);

            half3 intersectPlane(float3 dir,float3 rd,float4 ro,half3 colorA,half3 colorB,half3 baseCol, inout float t)
            {
                float t0=0;

                if(dot(dir,rd)>0)
                {
                    float3 wallPos=ceil(ro.w/_Distance)*_Distance*dir;
                    t0=dot(wallPos-ro.xyz,dir)/dot(rd,dir);
                    if(t0<t)
                    {
                        t=t0;
                        baseCol=colorA;
                    } 
                }
                else
                {
                    
                    float3 wallPos=(ceil(ro.w/_Distance)-1)*_Distance*dir;
                    t0=dot(wallPos-ro.xyz,dir)/dot(rd,dir);
                    if(t0<t)
                    {
                        t=t0;
                        baseCol=colorB;
                    } 
                }
                return baseCol;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float3 rd=normalize(i.viewDir);
                //偏移float3(0.5,0.5,0)使原点到中心
                //加rd*0.001是避免检测到正对摄像机的外表面
                float3 ro=i.objectPos-float3(0.5,0.5,0)+rd*0.001;

                float t=10000;
                half4 col=1;

                col.rgb=intersectPlane(up,rd,float4(ro,ro.y),_RoofColor,_FloorColor,col.rgb,t);
                col.rgb=intersectPlane(right,rd,float4(ro,ro.x),_WallColorA,_WallColorA,col.rgb,t);
                col.rgb=intersectPlane(forward,rd,float4(ro,ro.z),_WallColorB,_WallColorB,col.rgb,t);

                return col;
            }
            ENDCG
        }
    }
}

结果如图:

在这里插入图片描述
这里有几个需要注意的点:

  1. 这里根据论文将数据都转换到模型空间进行运算。
  2. 这个的视角方向是指向模型的所以要用反方向。
  3. 可以根据dot(dir,rd)的值判断是上表面还是下表面。

添加贴图

既然空间已经构建出来了,那加上贴图就是很简单的事情了,不同的轴向上使用不同的模型坐标去采样即可,代码如下:

Shader "MyShader/InteriorMappingTest"
{
    Properties
    {
        _WallTexA("Wall Texture A",2D)="white"{}
        _WallTexB("Wall Texture B",2D)="white"{}
        _RoofTex ("Texture", 2D) = "white" {}
        _FloorTex("Floor Texture",2D)="white"{}
        _Distance("Distance",Float)=0.2
    }
    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;
            };
            
            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 viewDir:TEXCOORD1;
                float3 objectPos:TEXCOORD2;
            };
            
            sampler2D _WallTexA;
            sampler2D _WallTexB;
            sampler2D _RoofTex;
            sampler2D _FloorTex;
            float _Distance;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                float3 worldPos=mul(unity_ObjectToWorld,v.vertex).xyz;
                float3 worldViewDir=UnityWorldSpaceViewDir(worldPos);
                o.viewDir=-mul(unity_WorldToObject,float4(worldViewDir,0));
                o.objectPos=v.vertex.xyz;

                return o;
            }

            static float3 up=float3(0,1,0);
            static float3 right=float3(1,0,0);
            static float3 forward=float3(0,0,1);

            float2 getUV(float3 pos,float3 dir)
            {
                return pos.xy*dir.z+pos.xz*dir.y+pos.zy*dir.x;
            }

            half3 intersectPlane(float3 dir,float3 rd,float4 ro,sampler2D texA,sampler2D texB,half3 baseCol, inout float t)
            {
                float t0=0;

                if(dot(dir,rd)>0)
                {
                    float3 wallPos=ceil(ro.w/_Distance)*_Distance*dir;
                    t0=dot(wallPos-ro.xyz,dir)/dot(rd,dir);
                    if(t0<t)
                    {
                        t=t0;
                        
                        float3 pos=ro+rd*t0;
                        pos=pos/_Distance;

                        baseCol=tex2D(texA,getUV(pos,dir));
                    } 
                }
                else
                {
                    
                    float3 wallPos=(ceil(ro.w/_Distance)-1)*_Distance*dir;
                    t0=dot(wallPos-ro.xyz,dir)/dot(rd,dir);
                    if(t0<t)
                    {
                        t=t0;

                        float3 pos=ro+rd*t0;
                        pos=pos/_Distance;

                        baseCol=tex2D(texB,getUV(pos,dir));
                    } 
                }
                return baseCol;
            }

            fixed4 frag (v2f i) : SV_Target
            {
                float3 rd=normalize(i.viewDir);
                float3 ro=i.objectPos-float3(0.5,0.5,0)+rd*0.001;

                float t=10000;
                half4 col=1;

                col.rgb=intersectPlane(up,rd,float4(ro,ro.y),_RoofTex,_FloorTex,col.rgb,t);
                col.rgb=intersectPlane(right,rd,float4(ro,ro.x),_WallTexA,_WallTexA,col.rgb,t);
                col.rgb=intersectPlane(forward,rd,float4(ro,ro.z),_WallTexB,_WallTexB,col.rgb,t);

                return col;
            }
            ENDCG
        }
    }
}

在这里插入图片描述

使用Cubemap

上面的代码又是if又是每个面都采样一次的,消耗自然不会低。既然是个正方形,那么自然而然就想到了用CubeMap和射线AABB盒相交检测Unity论坛上也有人做了,只是写得有点弯弯绕绕,而且用的世界空间,这里我对代码稍微进行了下修改,看起来更直观些。

Shader "Unlit/BoxProjection"
{
    Properties
    {
        _Cube ("Reflection Cubemap", Cube) = "_Skybox" {}
        _EnvBoxStart ("Env Box Start", Vector) = (0, 0, 0)
        _EnvBoxSize ("Env Box Size", Vector) = (1, 1, 1,1)
    }
    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;
            };
            
            struct v2f
            {
                float2 uv : TEXCOORD0;
                float4 vertex : SV_POSITION;
                float3 viewDir:TEXCOORD1;
                float3 objectPos:TEXCOORD2;
            };


            samplerCUBE _Cube;
            float4 _Cube_ST;
            float4 _EnvBoxStart;
            float4 _EnvBoxSize;
            
            v2f vert (appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv = v.uv;
                float3 worldPos=mul(unity_ObjectToWorld,v.vertex).xyz;
                float3 worldViewDir=UnityWorldSpaceViewDir(worldPos);
                o.viewDir=-mul(unity_WorldToObject,float4(worldViewDir,0));
                o.objectPos=v.vertex.xyz;
                return o;
            }
            
            fixed4 frag (v2f i) : SV_Target
            {
                float3 viewDir=i.viewDir;
                float3 objectPos=i.objectPos+half3(0.5,0.5,0);

                float3 rbmax=(_EnvBoxStart+_EnvBoxSize-objectPos)/viewDir;
                float3 rbmin=(_EnvBoxStart-objectPos)/viewDir;

                float3 t2=max(rbmin,rbmax);
				
				//远相交点
                float fa=min(min(t2.x,t2.y),t2.z);
                float3 posNobox=objectPos+viewDir*fa;
                //反射方向
                float3 reflectDir=posNobox-(_EnvBoxStart+_EnvBoxSize/2);

                fixed4 col = texCUBE(_Cube,reflectDir);
                return col;
            }
            ENDCG
        }
    }
}

我之前有看到网易的天谕静态反射部分用的就是类似的技术,Unity论坛上也是讨论用来做反射。

在这里插入图片描述

Tilling And Offset

这部分Unity论坛上也有大佬讨论和完整代码,完整代码我就不贴了,可以点链接去看。我只说我不懂那部分,或者说一开始没看懂的部分。

				// room uvs
                float2 roomUV = frac(i.uv);
 
                // raytrace box from tangent view dir
                float3 pos = float3(roomUV * 2.0 - 1.0, 1);
                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;

他这个求交部分我很久没弄懂,直到我翻到了Shadertoy的正方形实例部分,于是豁然开朗。

在这里插入图片描述
它这个代码还对墙面的采样做了随机翻转降低了重复感。

在这里插入图片描述
剩下的比如添加玻璃效果、调整深度、添加灯光、添加人物、更甚至添加阴影什么的就不在本文的讨论范围里了,可以去看看开头那两篇知乎大佬的文章。这里再贴个英文的链接,里面有贴图(demo,我不知道用什么写的,我只用了他的贴图)的下载地址Interior Mapping: Rendering Real Rooms Without Geometry

我把贴图传到了Github上。

请添加图片描述

  游戏开发 最新文章
6、英飞凌-AURIX-TC3XX: PWM实验之使用 GT
泛型自动装箱
CubeMax添加Rtthread操作系统 组件STM32F10
python多线程编程:如何优雅地关闭线程
数据类型隐式转换导致的阻塞
WebAPi实现多文件上传,并附带参数
from origin ‘null‘ has been blocked by
UE4 蓝图调用C++函数(附带项目工程)
Unity学习笔记(一)结构体的简单理解与应用
【Memory As a Programming Concept in C a
上一篇文章      下一篇文章      查看所有文章
加:2022-03-06 13:29:43  更:2022-03-06 13:29:49 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 16:38:51-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码