Muli3D <9> CubeTexture的采样原理
来源:互联网 发布:浙江省嘉善县 淘宝 编辑:程序博客网 时间:2024/05/17 19:17
记录一下,
在Muli3D中实现一个 采样 cubeTexture 的函数
result CMuli3DCubeTexture::SampleTexture( vector4 &o_vColor, float32 i_fU, float32 i_fV, float32 i_fW, const vector4 *i_pXGradient, const vector4 *i_pYGradient, const uint32 *i_pSamplerStates ){// Determine face and local u/v coordinates ...// source: http://developer.nvidia.com/object/cube_map_ogl_tutorial.html// major axis // direction target sc tc ma // ---------- --------------------------------- --- --- --- // +rx GL_TEXTURE_CUBE_MAP_POSITIVE_X_EXT -rz -ry rx // -rx GL_TEXTURE_CUBE_MAP_NEGATIVE_X_EXT +rz -ry rx // +ry GL_TEXTURE_CUBE_MAP_POSITIVE_Y_EXT +rx +rz ry // -ry GL_TEXTURE_CUBE_MAP_NEGATIVE_Y_EXT +rx -rz ry // +rz GL_TEXTURE_CUBE_MAP_POSITIVE_Z_EXT +rx -ry rz // -rz GL_TEXTURE_CUBE_MAP_NEGATIVE_Z_EXT -rx -ry rzfloat32 fCU, fCV, fInvMag;m3dcubefaces Face;const float32 fAbsU = fabsf( i_fU );const float32 fAbsV = fabsf( i_fV );const float32 fAbsW = fabsf( i_fW );if( fAbsU >= fAbsV && fAbsU >= fAbsW ){if( i_fU >= 0.0f ){// major axis direction: +rxFace = m3dcf_positive_x;fCU = -i_fW; fCV = -i_fV; fInvMag = 1.0f / fAbsU;}else{// major axis direction: -rxFace = m3dcf_negative_x;fCU = i_fW; fCV = -i_fV; fInvMag = 1.0f / fAbsU;}}else if( fAbsV >= fAbsU && fAbsV >= fAbsW ){if( i_fV >= 0.0f ){// major axis direction: +ryFace = m3dcf_positive_y;fCU = i_fU; fCV = i_fW; fInvMag = 1.0f / fAbsV;}else{// major axis direction: -ryFace = m3dcf_negative_y;fCU = i_fU; fCV = -i_fW; fInvMag = 1.0f / fAbsV;}}else //if( fAbsW >= fAbsU && fAbsW >= fAbsV ){if( i_fW >= 0.0f ){// major axis direction: +rzFace = m3dcf_positive_z;fCU = i_fU; fCV = -i_fV; fInvMag = 1.0f / fAbsW;}else{// major axis direction: -rzFace = m3dcf_negative_z;fCU = -i_fU; fCV = -i_fV; fInvMag = 1.0f / fAbsW;}}// s = ( sc/|ma| + 1 ) / 2 // t = ( tc/|ma| + 1 ) / 2fInvMag *= 0.5f;const float32 fU = /*fSaturate*/( fCU * fInvMag + 0.5f );const float32 fV = /*fSaturate*/( fCV * fInvMag + 0.5f );return m_ppCubeFaces[Face]->SampleTexture( o_vColor, fU, fV, 0, i_pXGradient, i_pYGradient, i_pSamplerStates );}
这个原理主要是这样的,来自于:https://scalibq.wordpress.com/2013/06/23/cubemaps/
Cubemap addressing
A cubemap is a collection of 6 square 2D textures, each mapped to a face of a cube (hence the name). It can be visualized like this:
For a texture lookup, a 3D vector is used as a texture coordinate. Think of this vector as a ray from the center of the cube, going through one of the faces. The texture is sampled at the intersection of the ray with the face. Or more intuitively: the viewer is at the center of the cube, looking in a certain direction. The vector is that direction.
Cubemaps are often previewed in 2D in a cross arrangement, like this:
Here you can clearly see how the 6 faces encode an entire environment, for the full 360 degrees. On MSDN there is a nice overview of how the texture coordinates are mapped out:
As you can see, the cube is axis-aligned, so the faces correspond with the X, Y and Z axis, where each axis has a negative and a positive face in the cubemap (-x, +x, -y, +y, -z and +z respectively).
It does not explain how the texture coordinate are actually calculated however. You can find that on an old nVidia page covering cubemaps though (although it is OpenGL-oriented, so it uses (S,T) rather than (U,V) as texture coordinates). The texture mapping works in two stages:
1) The face is selected by looking at the absolute values of the components of the 3d vector (|x|, |y|, |z|). The component with the absolute value of the largest magnitude determines the major axis. The sign of the component selects the positive or negative direction.
2) The selected face is addressed as a regular 2D texture with U, V coordinates within a (0..1) range. The U and V are calculated from the two components that were not the major axis. So for example, if we have +x as our cubemap face, then Y and Z will be used to calculate U and V:
U = ((-Z/|X|) + 1)/2
V = ((-Y/|X|) + 1)/2
Since X had the largest magnitude, dividing Y and Z by |X| will bring them within a (-1..1) range. This is then translated to a (0..2) range, and finally scaled to (0..1) range. Now we can do a regular 2D texture lookup. You can work out the formulas for the other 5 faces by looking at the mappings above. You can see which axis maps to U and V in each case, and you can see the direction in which the axis goes, compared to U/V, to see whether you need to flip the sign or not.
Note that since the texture coordinates are scaled by the major axis, it is not required to normalize the 3D vector. Regardless of the length of the vector, the coordinates will always end up in (0..1) range. Normalizing has no effect, it is just another scaling operation, which is made redundant by the texture coordinate calculation. In fact, on early hardware, cubemaps were often used to normalize vectors for per-pixel operations (the cubemap would just contain a normalized vector encoding the lookup vector for each texel). This early hardware would either not be able to perform a normalization per-pixel at all, or a cubemap lookup may have been cheaper than an arithmetic solution.
Note also that the texture coordinates have to be calculated on a per-pixel basis. If you were to do this per-vertex, you might have the problem that not all three vertices will look at the same face, and therefore not all vertices will address the same 2D texture. The face can change at any point inside a triangle.
Dynamic cubemaps
There are other types of environment maps than cubemaps. A popular one is the spherical map:
This type of environment map was very popular in the early days, because it is very easy to calculate texture coordinates for it, without requiring special hardware support (and also quite efficient in software, very popular for faking phong shading in the early 90s). Namely, if you take a normalized vector (x,y,z) as your view direction, you can derive the texture coordinates like this:
U = (x + 1) / 2
V = (y + 1) / 2
However, the cubemap has some advantages over other environment mappings. As you can already see in this spherical image, the resolution is very poor towards the edges. A cubemap has a very uniform mapping of pixels in all directions.
Another advantage is that cubemaps can very easily be updated in realtime with render-to-texture. You can render an environment map by just doing a renderpass for each cube face. Each face is square, and there are 4 faces going round horizontally in 360 degrees (front, right, back, left), so each face covers a 90 degree viewing angle (and then the extra 2 faces going round vertically, top and bottom). All you have to do is set up a camera positioned where you want the center of your environment map to be, facing in the right direction, with field-of-view of 90 degrees horizontally and vertically, and render to the respective face.
For dynamically updating other types of maps, such as spherical maps, you would first need to render to a set of 2D textures, and then you would need an extra pass with a special mesh to compose them into a spherical map with the correct warping.
Previewing a cubemap in realtime
I wanted to create a 2D preview of the contents of a cubemap, in the usual cross arrangement. The first idea then would be to just create 6 quads for the cross geometry, with 2D textures on them. So, a cubemap consists of 2D textures. Or does it? Well, conceptually it does. But not all 3D APIs actually let you manipulate the individual faces as textures. That is the problem I ran into. In Direct3D 10+, there is no specific cubemap datatype. There is a generic texture array datatype, and if you create an array of 6 2D textures, it can be used as a cubemap. But it’s still an array of 2D textures, so you can still use the faces directly as regular 2D textures (or rendertargets).
In Direct3D 9 however, a cubemap is a specific type of texture. You can access the individual faces as surfaces, but not as textures. You could create a set of 2D textures of the same dimensions, and then copy each cubemap surface to a texture, but that will clearly have some extra overhead.
So instead I wanted to map the cubemap onto the cross directly. For that, I had to derive the proper 3D vectors at each vertex of the cross. Since we already know that they do not have to be normalized, it can be done within the range of (-1..1) for each vector component, which is more intuitive than having to deal with normalized vectors.
So, I assigned indices to each vertex in the layout, and worked out which vertices fit to where, and then derived the components of the view vector. The indices are in orange, the view vectors in blue:
Every view vector is shared by 3 faces. So we need to have the following sets of vertices that share the same vector:
- 0, 2, 6
- 1,5
- 7, 11, 12
- 10, 13
The rest of the vertices are interior to the cross, and sharing is done automatically.
Normally my vertex formats would only have 2D texture coordinates. If I were to store the view vector as texture coordinates, I would need to define a new vertex format. However, since the per-vertex normal has no meaning for this preview mesh, I decided to just encode the view vector in the normalvector of each vertex instead. This way I would not need any custom vertex format for a cubemap preview. I would just need a pixel shader that sampled the cubemap with the normal vector.
For the positions, I decided to put the cross inside a unit square. The width has a (-0.5..0.5) range. The height has a (-0.375..0.375) range, since it is 4 faces wide but only 3 faces high. This gives me the following vertices (this is the list I was hoping to find and just copy-paste into my own code):
vertexBuffer[] = {{ -0.25f, 0.375f, 0.0f, -1, 1, -1 },{ 0.0f, 0.375f, 0.0f, 1, 1, -1 },{ -0.5f, 0.125f, 0.0f, -1, 1, -1 },{ -0.25f, 0.125f, 0.0f, -1, 1, 1 },{ 0.0f, 0.125f, 0.0f, 1, 1, 1 },{ 0.25f, 0.125f, 0.0f, 1, 1, -1 },{ 0.5f, 0.125f, 0.0f, -1, 1, -1 },{ -0.5f, -0.125f, 0.0f, -1, -1, -1 },{ -0.25f, -0.125f, 0.0f, -1, -1, 1 },{ 0.0f, -0.125f, 0.0f, 1, -1, 1 },{ 0.25f, -0.125f, 0.0f, 1, -1, -1 },{ 0.5f, -0.125f, 0.0f, -1, -1, -1 },{ -0.25f, -0.375f, 0.0f, -1, -1, -1 },{ 0.0f, -0.375f, 0.0f, 1, -1, -1 }};
And with that mesh, I can finally preview my cubemaps directly. It is an elegant solution as well, compared to having to use 6 separate textures (which means you need to either abuse multitexture heavily, or render each plane with a separate call, because you have to switch textures). And it works for all 3D APIs, since you are actually using the cubemap itself, and there is no need for a workaround copying the texture to a separate 2D texure.
- Muli3D <9> CubeTexture的采样原理
- 第二次采样的原理
- FFT与采样点数的关系原理
- ArcGIS教程:“采样”的工作原理
- 单片机A/D采样的原理
- Muli3D <6> Struct m3dtriangleinfo 的属性 fZDdx,fZDdy 的推导
- Muli3D <5> 判断点与面的关系
- Muli3D <7> 判断Ray与Sphere的关系
- 采样原理及其应用
- 采样示波器和实时示波器的原理与各自优势
- 音频重采样造成音质损失的原理
- word2vec基于负采样的模型原理介绍
- 过采样技术原理介绍
- 过采样技术原理介绍
- Muli3D <8> 计算Shader中顶点属性相对于屏幕坐标的偏导数
- 示波器的采样频率与采样深度
- 采样点数与采样频率的区别
- 音频的参数--采样位数、采样频率
- ORACLE 临时表空间使用率过高的原因及解决方案
- iOS 扫描二维码
- 实践---java实现生成验证码功能
- xcode命令行编译
- PAT甲级1002
- Muli3D <9> CubeTexture的采样原理
- 读取properties配置文件信息
- 解决mac上启动tomcat时出现Local host name unknown异常
- Java基础:数组Array转成List的几种方法
- warning: 远程 HEAD 指向一个不存在的引用,无法检出(warning: remote HEAD refers to nonexistent ref, unable to checkout)
- hadoop1.x:hdfs
- HDU 1686 Oulipo
- Retrofit2+RxJava2依赖包一览
- 判断机器是大端还是小端