天空盒(SkyBox)的实现原理与细节
天空盒的原理
在实时渲染中,如果要绘制非常远的物体,例如远处的山、天空等,随着观察者的距离的移动,这个物体的大小是几乎没有什么变化的,想象一下远处有一座山,即使人走进十米、百米、甚至千米,这座山的大小也是几乎不怎么改变的,这个时候可以考虑采用天空盒技术。
所谓的天空盒其实就是将一个立方体展开,然后在六个面上贴上相应的贴图,如上图所示。
在实际的渲染中,将这个立方体始终罩在摄像机的周围,让摄像机始终处于这个立方体的中心位置,然后根据视线与立方体的交点的坐标,来确定究竟要在哪一个面上进行纹理采样。具体的映射方法为:设视线与立方体的交点为(x,y,z)(x,y,z)(x,y,z),在x、y、zx、y、zx、y、z中取绝对值最大的那个分量,根据它的符号来判定在哪个面上采样。
然后让其他两个分量都除以最大分量的绝对值,这样就让另外两个分量都映射到了[0,1][0,1][0,1]内,然后就可以直接在对应的纹理上做纹理映射就行了,这个方法就是所谓的Cube Map,是天空盒方法的核心。以下是它的Vertex Shader和Pixel Shader,在Direct3D中可以直接用TextureCube,这样就省去了做Cube Map的步骤。
cbuffer MatrixType
{matrix WorldMatrix;matrix ViewMatrix;matrix ProjectionMatrix;
};struct VertexInputType
{float4 position : POSITION;float2 tex : TEXCOORD0;
};struct PixelInputType
{float4 position : SV_POSITION;float3 tex : TEXCOORD0;
};PixelInputType main(VertexInputType input)
{PixelInputType output;input.position.w = 1.0f;output.position = mul(input.position,WorldMatrix);output.position = mul(output.position,ViewMatrix);output.position = mul(output.position,ProjectionMatrix);output.position.z = output.position.w;output.tex.x = input.position.x;output.tex.y = input.position.y;output.tex.z = input.position.z;return output;
};
TextureCube shaderTexture;
SamplerState SampleType;struct PixelInputType
{float4 position : SV_POSITION;float3 tex : TEXCOORD0;
};float4 PixelFunc(PixelInputType input) : SV_TARGET
{return shaderTexture.Sample(SampleType,input.tex);
};
天空盒的原理非常简单,下面来探讨关于天空盒的几个细节问题。
z = w
在投影变换之后,会做一步透视除法,即让四元向量的所有分量都除以它的W分量,从而使视锥体内的区域的x、y映射到[−1,1][−1,1][-1,1],z映射到[0,1][0,1][0,1],从而根据透视除法之后的x、y、zx、y、zx、y、z的范围直接剔除掉那些不可见的顶点,如果令z=wz=wz=w,就表示透视除法后的z=1z=1z=1,也就是让天空盒始终处于远平面的位置,从而让它被之后所有要绘制的物体遮挡住。由于z=1z=1z=1,要修改深度测试的比较函数,即令 CD3D11_DEPTH_STENCIL_DESC 的desc.DepthFunc = D3D11_COMPARISON_LESS_EQUAL,如果比较函数是小于的话,由于深度缓冲的最大值本来就是1,无法通过深度测试的比较函数,最终天空盒是没办法被绘制出来的。
消隐问题
由于摄像机位于物体的内部,这个时候消隐的设置尤为重要,模型本身是没有发生变化的,它的法线始终朝向外部,那么视线与法线的夹角就变成了锐角,如果采用背面消隐,整个模型就会都被剔除掉,所以在绘制天空盒的时候,要么取消消隐,要么将消隐设置为正面消隐。
天空盒模型大小的限制
原则上模型的大小对于最终的结果没有影响,因为在模型增大的时候,虽然它的面是变大了,但它离视锥也变远了,因此最终视野内的大小是保持不变的,但是一定要保证模型始终处在视锥体的内部,即让模型离摄像机最远的点到摄像机的距离不超过摄像机到远平面的距离,对于一个立方体模型来说,离中心最远的点显然是它的顶点,
d=\frac{\sqrt{3}}{2}a aaa为立方体的边长,由
d≤Z_{far}得
a≤\frac{2}{\sqrt{3}}Z_{far}因此天空盒的模型边长有一个上界,在这个最大值以内都是可以的。
天空盒纹理的最佳大小
最终渲染的效果很大程度上取决于纹理大小与屏幕大小之间的关系,如果要达到最佳的效果,肯定是要让纹理中的每一个纹素对应窗口中的每一个像素,此时纹理大小与窗口大小的关系为:
T_{size} = \frac{R_{width}}{tan({fov/2})}其中 TsizeTsizeT_{size}为纹理的大小, RwidthRwidthR_{width}为窗口的宽度, fovfovfov为视锥体的视角。
下面来进行推导:
取远平面上的两点,设它们的x坐标分别为 x1x1x1、 x2x2x2,这两点显然就是天空盒上的两点。
现在计算它们归一化后在窗口屏幕上的坐标:
r_{x1} = \frac{(\frac{x1}{d*tan(fov/2)} + 1.0)}{2}*r_{width}
解释一下:其中 x1d∗tan(fov/2)x1d∗tan(fov/2)\frac{x1}{d*tan(fov/2)}将 x1x1x1映射到 [−1,1][−1,1][-1,1],再加1并除以2就映射到了 [0,1][0,1][0,1]
同理
r_{x2} = \frac{(\frac{x2}{d*tan(fov/2)} + 1.0)}{2}*r_{width}
现在,假设x1、x2映射到屏幕上后两者的x坐标差1,即相隔1个像素:
r_{x2}-r_{x1} = 1 代入上面两式可得:
\frac{(\frac{x2}{d*tan(fov/2)}) -(\frac{x1}{d*tan(fov/2)})}{2}*r_{width} = 1即
x2-x1=\frac{2*d*tan(fov/2)}{r_{width}}\tag1
由之前所述,最佳情况是屏幕上的一个像素对应纹理中的一个纹素,那么就意味着x1、x2映射到纹理中也应该差1。
由CubeMap的方法:
T_{x1} = \frac{\frac{x1}{d}+1}{2}*T_{size}
T_{x2} = \frac{\frac{x2}{d}+1}{2}*T_{size}
由
T_{x2}-T_{x1} = 1得:
x2-x1=\frac{2*d}{T_{size}}将其带入(1)式即可得
T_{size} = \frac{R_{width}}{tan({fov/2})}
有的时候可能纹理的大小被局限在256*256以内,那么对于一些较大的窗口来说,最终纹理会被拉伸,影响最终的渲染结果。如果对质量要求不高,很多情况下这样拉伸的结果也是可以接受的,但如果对质量有一定的要求的话,可以对天空盒做一个分块处理,保留原来的CubeMap方式不变,在做纹理映射的时候,将其分成几个小块来处理,每个小块分别是合适大小的纹理,如图所示:
天空盒(SkyBox)的实现原理与细节相关推荐
- CSharpGL(43)环境映射(Environment Mapping)-天空盒(Skybox)反射(Reflection)和折射(Refraction)...
CSharpGL(43)环境映射(Environment Mapping)-天空盒(Skybox)反射(Reflection)和折射(Refraction) 开始 如图所示,本文围绕GLSL里的sam ...
- Kafka的结构、特点和原理(细节)
一.Kafka的结构 重点在于Broker的结构.每一个消息归宿于特定的Broker下的特定的Topic下特定的Partion.而这些对应关系则被ZooKeeper记录下来. 二.特点 异步通信 一种 ...
- LSM零知识学习六、插桩原理实现细节(4)
接前一篇文章:LSM零知识学习五.插桩原理实现细节(3) 本文内容参考: LSM(Linux Security Modules)框架原理解析_lsm linux_pwl999的博客-CSDN博客 特此 ...
- LSM零知识学习四、插桩原理实现细节(2)
接前一篇文章:LSM零知识学习三.插桩原理实现细节(1) 本文内容参考: LSM(Linux Security Modules)框架原理解析_lsm linux_pwl999的博客-CSDN博客 特此 ...
- WaveNet相关原理及细节介绍
Neural vocoder层出不穷, 但是WaveNet仍然是重中之重.作为后续变种的基础和参考对比目标,还是需要先对WaveNet进行比较深入的了解,才能为后续演变后的vocoder的学习打下基础 ...
- 你知道kernel version的实现原理和细节吗
引言 kernel 启动时通常会看到下面第二行信息的内容,它们代表了当前 kernel 的版本.编译工具版本.编译环境等信息. Booting Linux on physical CPU 0x0 Li ...
- 以太坊BIP39助记词到公钥地址的原理与细节
以太坊基础-你真的懂吗 以太坊私钥 eg: fad9c8855b740a0b7ed4c221dbad0f33a83a49cad6b3fe8d5817ac83d38b6a19 由256位:不考虑0x前缀 ...
- Unity3D 里怎么制作天空盒(skybox)
第一步 新建一个材质球 第二步 点击材质球的Shader-->Skybox/6 sided----->插入6张天空图片 第三步点击 MainCamera 第四步 在MainCamera ...
- 「实战」蘑菇街 PC 端首页,瀑布流布局的实现原理与细节技巧
作者:蘑菇街前端团队 链接:https://juejin.im/post/5e05acf0f265da33d158a1b1 零.介绍 这篇文章主要是介绍网站页面瀑布流布局的实现,主要包括: 瀑布流是什 ...
- louvain算法python_复杂网络任务6:Louvain社区发现算法的原理、细节和实现,作业,六,以及...
Δ Q = [ ∑ i n 2 ∗ m + k i , i n 2 ∗ m − ( ∑ t o t 2 ∗ m ) 2 − ( 2 ∗ ∑ t o t ∗ k i 4 ∗ m 2 ) − ( k i ...
最新文章
- 为什么优秀的程序员都成了无能的领导?
- 成功解决ImportError: Could not find ‘cudart64_90.dll‘. TensorFlow requires that this DLL be installed in
- 一个抓取电脑屏幕的小控件台程序
- android 代码加view,Android中将View添加至窗口的源码分析
- [BUUCTF-pwn]——ciscn_2019_s_3
- JavaScript 几种排序算法实现(冒泡、选择、插入、归并、快速排序)
- 计算机数值计算的相关文章,数值计算论文.doc
- ssm后台数据是为什么是空值_网易后台开发实习生面试总结
- android随机抽奖代码_用Excel实现不放回随机抽样
- 实战 使用Java开发简易小游戏:贪吃蛇(附源码!)
- matlab 求矩阵各行的平均值
- idea快速创建包快捷键大全_idea快捷键大全
- 一个能启动电脑的U盘
- 试题 算法训练 盾神与离散老师2
- 中文puppy linux7.5,Puppy Linux 7.5发布,支持UEFI启动的
- hive报错整理之Malformed ORC file 、Invalid postscript.
- centos高清分辨率
- 屏幕左上右下坐标计算
- 关于解压 tar.gz的问题
- 深入理解LINUX内核(影印版第3版)》的笔记
热门文章
- 「项目分享」软件测试简历中项目怎么写?从候选人中脱颖而出,offer拿到手软
- 计算机服务里打印功能停止,win7系统电脑打印机print spooler服务总是自动停止的解决方法...
- 全面理解网络流中的最大流问题
- 网络编程_8(项目附件)
- Anaconda下载安装
- Java数据库的介绍和使用
- 西门子plc语句表是c语言吗,三菱、西门子PLC常用语句表,速来收!
- 怎么把度分秒化成小数_角度的度分秒与小数点格式互相转换
- 计算机408考研 思维导图 知识整理
- 中科大EPC自动程序(2022python版)