This sample implements displacement mapping by ray tracing through geometry extruded from a base mesh. The sample uses Direct3D 10 and the geometry shader to extrude prisms which are then decomposed into three tetrahedra for each triangle in the mesh. The pixel shader ray traces through the tetrahedra to intersect the displacement map. The diffuse color and normal values are then calculated for this point and used to set the final color of the pixel on screen. This sample is an adaptation of a paper from Microsoft Research Asia ( Wang, Xi, Xin Tong, Stephen Lin, Shimin Hu, Baining Guo, and Heung-Yeung Shum. 2004. Generalized Displacement Maps. In Eurographics Symposium on Rendering 2004, pp. 227-234. )
这个Sample通过对一个原始mesh进行几何抽取并进行光线跟踪计算实现了替换贴图(displacement map)。Sample使用D3D10和GS对mesh的每个三角形表面抽出棱柱并把它们分解为三个四面体。PS对这些四面体进行光线跟踪并与Displacement map相交,求得交点后计算该点的漫光和法向并把最终颜色输出到屏幕。这个sample是对微软亚洲研究院的一篇论文修改后的实现(Wang?,Xi?,童欣,郭百宁,Stephen Lin,Shimin Hu,沈向洋等,2004,Generalized Displacement Maps。Enrographics 2004绘制专题会议,227-234页)。
Displacing mapping: 就是在一张纹理上保存物体表面凹凸不平的信息,在绘制中采样这张纹理得到物体的实际几何信息完成绘制。通常Displacement mapping作为Vertex Shader的纹理使用。
There have been many approaches to displacement mapping in recent history. Many rely on tessellating the geometry to a high level of detail and then displacing this geometry based upon a height value stored in a height map. DisplacementMapping10 uses a different approach. Instead of producing detail by displacing highly tessellated geometry, the sample creates three tetrahedra for every input triangle and uses a per-pixel ray tracing approach to evaluate the geometry.
在历史上有许多种方法实现displacement mapping。大部分方法依赖于将物体几何细化到高层次细节,然后使用存在一张高度图中的高度值来改变表面细节的几何信息。DisplacementMapping10使用不同的方法。Sample通过对每个输入的三角形创建3个四面体并用逐像素光线跟踪来计算几何来替代传统的高度细化几何创建层次细节的方法。
For every triangle on the mesh, a triangular prism is extruded. The prism is constructed by extruding the vertices of the triangle along the directions of their normals by an amount specified to be the maximum displacement. The XY texture coordinates at the top of the prism remain the same as the bottom; however, the Z coordinate becomes 1 to represent the maximum displacement. A naive approach would be to ray trace through this extruded prism by finding the texture coordinates where the eye ray enters and exits the prism. By taking an evenly spaced sampling of a height map texture between the entry and exit points, one could easily determine where the ray intersects the height map.
Sample对mesh上的每个三角形抽取一个三棱柱。棱柱通过把三角形三个顶点沿着法向方向平移一定距离建立,偏移距离能使棱柱达到最大的displacement距离。棱柱顶部的XY纹理坐标和底部一样,Z坐标变为1代表最大的displacement距离。要找到视线射入和射出三棱柱处的纹理坐标,最简单的方法是使用光线跟踪对视线和三棱柱求交。通过对高度图纹理在入射点和出射点附近均匀空间采样,我们可以简单确定光线在何处和高度图相交。
Unfortunately, there are two problems with this approach.
Problem 1: Because of varying curvature in the mesh, the four points that make up each side of the prism are not guaranteed to be coplanar. When extruding the prism, each side will be split into two triangles. Any neighboring face will also share this edge as an edge of its extruded prism. If the neighboring triangle does not create the sides of its prism in a way that correlates to how the current triangle creates the same edge, cracks may appear in the final rendering.
不幸的是,这种方法有两个问题。
问题1:因为mesh表面曲率变化非常大,构建三棱柱各个面的四个顶点不一定是共面的。当抽取三棱柱时,每个面会被分为两个三角形。任何两个相邻表面会把中间这条边作为抽取三棱柱的边来共享。如果邻接三角形没有通过和当前三角形创建这条边相对应的方式来生成三棱柱的表面,在最终绘制就就会出现裂痕。
Problem 2: It's very hard to calculating the exit texture coordinate for an eye ray traveling through a triangular prism. The entry point, on the other hand is easy. Because of depth-buffering, the nearest triangle to the eye will always be drawn. The graphics hardware will automatically interpolate the entry texture coordinate across the draw faces. The rear exit point is a different story. One could do ray-plane intersections to get the distance to the rear of the prism. This is a good start, but still doesn't give us the texture coordinates. Calculating barycentrics for a triangle are costly in the shader. One could pass down an un-normalized tangent basis frame (TBN matrix) and transform the view vector into this frame, add it to the input texture coordinate, and achieve the exit texture coordinate. Unfortunately, this breaks down when the mesh deforms since under high deformation, the un-normalized tangent frame is non-constant across the prism.
问题2:计算每条视线穿过三棱柱后出射点的纹理坐标是非常困难的。但是计算入射点却很简单。因为有深度缓存,离视点最近的三角形都会被画出来。图形硬件会自动对入射点纹理坐标在绘制表面上进行线性插值。出射点的算法则完全不一样。可以通过线-面相交计算来得到视点到三棱柱背面的距离。这样做有一定道理,但是得不到纹理坐标。在Shader里对三角形计算中心也是非常耗时的。也可以通过传入一个非归一化的基于切向的结构(使用TBN矩阵)并把视点向量转换到这个结构上,把它加到输入纹理坐标上获得输出的纹理坐标。不幸的是,当mesh在高度变形的情况下它就会失效,因为在非归一化的切向结构在变形中并不是保持常数。
The two problems above can be solve by decomposing the the prism into three tetrahedra and by using the VertexID generated by the input assembler to order the vertices when creating the tetrahedra.
上述两个问题可以通过把三棱柱分为三个四面体来解决。使用IA阶段生成的VertexID值对顶点排序可以创建这些四面体。
The distance to the rear of a tetrahedron is computed by intersecting the eye ray with the planes of the tetrahedron facing away from the eye and choosing the smallest distance. Now, the only problem left is to determine the texture coordinates at the exit point.
到四面体背面的的距离可以通过把视线和背离视点的四面体表面相交并从中选择一个最小距离来得到。现在问题只剩下怎么确定射出点的纹理坐标。
A nice property of a tetrahedron is that by using the texture coordinates of the 4 points, a constant texture gradient can be found across the tetrahedron. By using this gradient, the entry texture coordinate (computed easily during rasterization), and the distance to the rear of the tetrahedron, the shader can compute the exit texture coordinates.
四面体的一个很好的属性就是使用它四个顶点的纹理坐标可以计算出一个穿过四面体的常数纹理梯度。使用梯度值,输入点纹理坐标(在光栅化过程中可以轻松得到)和到四面体背面的距离,shader就能算出射出点的纹理坐标。
Once the entry and exit texture coordinates are known, it's only a matter of stepping from one to the other. While stepping along the ray from the entry to the exit coordinates, the XY components are used to lookup into the displacement map. The Z component represents the height from the base triangle. This is compared against the height value returned by the lookup into the displacement map. If the Z component of the ray is less than the height returned by the displacement map, the sample assumes that an intersection has taken place and stop searching. If no intersection is found before reaching the exit texture coordinate, the pixel is discarded.
一旦射入点和射出点的纹理坐标都知道了,只需要一步步从射入点采样到射出点。多步采样时,XY通道作用采样displacement纹理,Z通道代表到基三角形的距离并和从displacement纹理中得到高度值做比较。如果Z通道小于采样值,就认为视线和四面体相交,停止搜索返回。如果在到达射出点之前没有找到交点,就剔除这个像素。
Once the intersection point is found, all that is left is to determine the color and normal at the point of intersection. Again, if no intersection point is found, the pixel is simply discarded. The diffuse color at the intersection point can be found by sampling the diffuse texture at the XY coordinates of the ray where the ray intersected the displacement map. While the displacement map provides enough information to determine the normal at the point of intersection (using finite differencing), this sample encodes a normal map as the rgb components of the displacement texture (displacement is stored in alpha). Thus, the last sample along the entry/exit ray contains the normal for the point of intersection.
一旦找到了交点,接下来就需要确定交点的法向和颜色。如果没有找到这个交点,就剔除这个像素。交点的漫反射颜色可以使用交点的XY通道来采样漫反射纹理得到。因为Displacement纹理提供了足够的信息来得到交点的法向(使用差分),它的rgb通道可以用来保存一张法向图(alpha通道用来保存高度)。因为最后沿着入射/出射点的采样结果包含了交点的法向信息。
One problem with this technique is also a problem with most displacement mapping techniques. In areas of high concave curvature, the extruded prism can actually fold in on itself and flip. This causes the tetrahedra and resulting ray trace to be incorrect. The solution that the sample employs is to detect these cases and reduce the amount of displacement for the offending triangle.
这种技术存在的一个问题也是所有displacement mapping技术的问题。如果某个区域曲面曲率很高,抽取的三棱柱会折叠入曲面内部并产生翻转。这会导致四面体和光线跟踪的结果不正确。解决的方法的是在采样时检测这种情况并减少这类三角形变形的数值。
Another problem with this technique is sampling artifacts. If the number of steps per ray is too small, some high-frequency details can be missed. For very low sampling rates, this can cause details to pop in and out as the camera or object moves. Although geometry based displacement mapping also has frequency issues, the connectivity between the triangles ensure that there is always a smooth transition between different height values. Furthemore, geometry based methods are not dependent on the orientation of the view ray, so no popping of detail occurs when the object or camera moves.
这个技术的另一问题时采样走样。如果每条光线采样的步数太少,一些高频的细节会被忽略。很低的采样率会导致在视点或者屏幕移动时,物体表面细节出现凹凸起伏的现象。虽然基于几何displacement变形算法的也有采样频率的问题,但是三角形之间的连接保证物体表面不同高度值之间仍然能够平滑变换。进一步说,基于几何的算法不依赖视线,所有在物体或视点移动时不会出现凹凸起伏的问题。
In this section, we compare traditional methods of achieving higher detail with the method this sample uses. The base mesh is 1480 polygons. For normal-mapping, the polygon count stays at 1480. For traditional displacement mapping, the mesh is uniformly tessellated to a high number of polygons and then the vertices are displaced according to the height of the displacement map. For ray-traced displacement (this sample), the mesh is converted into 3 tetrahedra (4 triangles each) in the geometry shader, such that 17760 triangles are actually passed to the pixel shader.
在这节中,我们把sample中的算法和传统算法在获得高层次细节上做比较。原始纹理是1480个多边形。对于法向贴图,多边形数目保持在1480。对于传统的displacemnt 贴图,mesh被不一致细化产生大量多边形并把顶点按照displacement纹理做移动。对于光线跟踪displacement mapping(这个sample中的算法),mesh在GS中被转换为3个四面体(每个4个顶点),所以传送到PS中的三角形数目为17760个。
Notice the lack of self occlusion or definition on the silhouette of the object.
注意物体表面没有自遮挡或者本影区域。
At 23680 triangles, the mesh still lacks some details. Notice the precision issues around the eyes.
在23680个三角形的情况下,mesh依然损失了一些细节。注意眼部周围的精度问题
This is a screenshot from the DisplacementMapping10 sample. It uses the ray traced displacement method described above. The eye ray is traced through the geometry shader created tetrahedra and intersected with the displacement map. This is from the base mesh of 1480 triangles that is expanded to 17760 triangles in the GS. Notice the level of detail compared to the traditional method using 23680 triangles.
这是一个sample的截图。它采用了上述的光线跟踪displacement算法。视线穿过了GS创建的四面体并和displacement map相交。原始mesh有1480个三角形,在GS中被扩展到17760个三角形。注意和使用23680个三角形mesh计算的传统displacement map算法比较层次细节。
This mesh was rendered using the traditional method of displacement by tessellating the mesh to 378880 triangles. Compare this to the image above rendered with 1480 (17760 triangles after the GS).
这是使用传统算法绘制的图像,mesh被细化为378880个三角形。可以和上面使用1480个三角形(GS中17760个三角形)做比较。
This sample implements motion blur using the geometry shader to extrude fins from the original mesh.
这个sample使用GS从原始mesh上抽取边缘面片来实现运动模糊。
The MotionBlur10 sample implements motion blur using a geometric approach combined with order independent transparency through the use of AlphaToCoverage and with anisotropic texture filtering.
MotionBlur10 sample使用几何方法并结合顺序独立透明计算(通过使用AlphaToCoverage和各异向行纹理采样)实现运动模糊。
Geometry is extruded along the path of motion. A triangle strip is extruded backwards and forwards in time such that the middle triangle is always at the current time. This is done by passing in an array of matrices each containing the transformation information for specific points in time. Namely, N equally spaced points of time ahead and N equally spaced points of time behind. The sample handles this in the application code by calculating the current time and then determining how far to go backwards in time based upon the length of time that the fictional "shutter" will stay open. The middle time in this array of matrices is where the object will look most "solid". This method allows the motion blur effect to be independent of frame rate. The only dependency is that the application must track or guess the locations of the objects at times in the past. Normally this is just a few fractions of a second into the past, but for dramatic effect, applications could blur over several seconds.
几何沿着运动的路径抽取。沿着运动时间抽取一条三角条带,中间三角形代表当前时间三角形的位置而前后的三角形分别代表过去和未来的三角形位置。这些通过传入一组代表一些特定时间点变换信息的矩阵得到,包括N个过去的等距时间点和N个未来的等距时间点。sample通过在程序中计算当前时间然后根据划定的时间长度确定往回计算多少步得到对应的三角形。这个矩阵数组中最中间的时刻也是这个物体看起来最实在的时刻。这种方法允许运动模糊独立于帧率。Sample对时间的唯一的依赖之处在于程序需要跟踪或者猜测物体在过去某个时间的位置。通常这只是过去一秒中很小的一部分时间,但是对于动态效果,程序能够模糊好几秒。
As each section of the triangle strip is extruded, it is given an alpha value based upon how much time has elapsed between the time represented by this section of the strip and the current time (the triangle in the middle of the forwards and backwards strip). Because this information is not transmitted back to the CPU, we do not have the option of using the CPU to sort the triangles for accurate alpha blending. Therefore, the sample uses AlphaToCoverage to fake order independent transparency. Please see the Instancing10 sample for a more detailed description of transparency using AlphaToCoverage.
在三角条带的每一段都被抽取出来后,它被赋予一个Alpha值,该值由这个条带代表的时间和当前时间的时间(处于过去条带和将来条带之间的三角形代表的时间)差来决定。因为这些信息没有被送回CPU,所以我们不能使用CPU对三角形排序计算精确的alpha混合。因此,这里使用AlphaToCoverage来实现顺序独立半透明效果。请参见Instancing10 sample了解AlphaToCoverage更多的细节描述。
The final trick to achieving the motion blur illusion is to blur the texture in the direction of motion when sampling. This is achieved by enabling anisotropic filtering and carefully controlling the ddx and ddy parameters to SampleGrad. These can be found by transforming the vector indicating the direction of motion into the texture space for that particular triangle.
获得运动模糊效果的最后一个trick是在采样纹理时沿着运动方向模糊纹理。这通过打开各异向性采样并仔细控制SampleGrad函数的ddx,ddy参数来实现。这些参数能够通过把表示运动方向的顶点对于某个特定的三角形转换到它纹理空间上得到。
// Find the projection of our motion into our tangent/texture space
Output.Aniso.y = max( 0.0001, abs( g_fTextureSmear*dot( clipTangent, clipMotionDir ) ) );
Output.Aniso.x = max( 0.0001, abs( g_fTextureSmear*dot( clipBiTangent, clipMotionDir ) ) );
Note that the g_fTextureSmear variable can be used to control how much the texture will stretch along the direction of motion. Setting this value too high can result in the anisotropic filter accessing part of the texture completely unrelated to the current texture coordinate.
注意到g_fTextureSmear变量能够用作控制纹理能够在运动方向上拉伸多少距离。如果这个值设的过高会导致各异向性采样完全和当前的纹理坐标无关。
This sample explains how to perform multi-stream rendering on Direct3D 9 and Direct3D 10 platforms. Multi-stream rendering allows vertex attributes to be split between different vertex streams. Multiple streams are referenced using a single index buffer. In addition, Direct3D 10 allows multi-stream and multi-index rendering, where multiple indices are used to access vertex data that is stored at frequencies. In this sample, the Direct3D 10 codepath references positions that are stored once per vertex and, in the same shader, references normals that are only stored once per triangle.
这个sample解释了如在在D3D9和D3D10中使用多数据流绘制。多数据流绘制允许顶点属性放置到不同的顶点数据流中,这些数据流使用同一个索引缓存。同时D3D10允许顶点数据多数据流和多索引流绘制,多个索引能够访问统一顶点数据多次。在这个sample中,D3D10编码路径索引位置保存在一个顶点中;但在相同的shader中,索引法向只保存在每个三角形中。
Multi-Stream Single-Index rendering renders geometry using multiple vertex streams that are indexed by the same index buffer. This means that all data must be stored at the same frequency (namely, data is stored per-vertex). For mult-stream single-index rendering, 4 input vertex buffers are created. They store position, normal, textures coordinates, and alternative texture coordinates respectively.
单索引多数据流绘制使用单个索引缓存来组织多个顶点数据。这意味着所有数据必须以相同的频率存放(就是说数据必须是逐顶点的)。多数据流单索引绘制需要创建4个输入顶点缓存分别保存位置,法向,纹理坐标和可替换的纹理坐标。
The first step is to create a vertex declaration that incorporates all of these individual vertex streams. The goal of this vertex declaration is to make the multiple individual streams look like one unified stream to the vertex shader.
第一步是创建顶点申明,把所有独立的顶点缓存连接起来。顶点申明的目的是让多个独立的数据流从vertex shader角度看像是一个数据流。
// Create a Vertex Decl for the MultiStream data.
// Notice that the first parameter is the stream index. This indicates the
// VB that the particular data comes from. In this case, position data
// comes from stream 0. Normal data comes from stream 1. Texture coordinate
// data comes from stream 2.
D3DVERTEXELEMENT9 declDesc[] =
{
{0, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0},
{1, 0, D3DDECLTYPE_FLOAT3, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_NORMAL, 0},
{2, 0, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0},
{0xFF,0,D3DDECLTYPE_UNUSED, 0,0,0}// D3DDECL_END
};
pd3dDevice->CreateVertexDeclaration( declDesc, &g_pDecl );
Before rendering the sample must make sure that all streams are bound, that the index buffer is bound, and that the correct vertex declaration is set. Notice that the third vertex stream ( stream number 2 ) can be switched between the original set of texture coordinates and the alternate set of texture coordinates without affecting the other streams.
在绘制之前,sample必须保证所有的数据流都被绑定,索引缓存也被绑定,正确的顶点申明被设置。注意第三个顶点数据流(流编号2)能够从原始纹理坐标和替换纹理坐标之间相互切换而不需要影响到其他数据流。
// Setup our multiple streams
pd3dDevice->SetStreamSource( 0, g_pVBs[ST_VERTEX_POSITION], 0, sizeof(D3DXVECTOR3) );
pd3dDevice->SetStreamSource( 1, g_pVBs[ST_VERTEX_NORMAL], 0, sizeof(D3DXVECTOR3) );
if(g_bUseAltUV)
pd3dDevice->SetStreamSource( 2, g_pVBs[ST_VERTEX_TEXTUREUV2], 0, sizeof(D3DXVECTOR2) );
else
pd3dDevice->SetStreamSource( 2, g_pVBs[ST_VERTEX_TEXTUREUV], 0, sizeof(D3DXVECTOR2) );
// Set our index buffer as well
pd3dDevice->SetIndices( g_pIB );
// Set A Multistream Vertex Decl insted of FVF
pd3dDevice->SetVertexDeclaration( g_pDecl );
This is all of the work necessary to facilitate multi-stream single-index rendering on Direct3D 9. The vertex shader is written as if all of the input came in from a single stream.
这是D3D9中进行多数据流单坐隐绘制的所有工作。Vertex shader可以写成把它们当作来自同一个数据流。
The setup is similar for performing multi-stream single-index rendering in Direct3D 10. The same vertex buffers are created (position, normal, texture coordinates, alternate texture coordinates). The sample then creates and input layout incorporating the separate vertex streams. Again, the purpose is to make the multiple vertex streams look like one stream to the vertex shader.
在D3D10中初始化多数据流单索引绘制和D3D9类似。创建相同的顶点缓存(位置,法向,纹理坐标,替换纹理坐标)。然后创建输入层,使VS认为它们是从一个流中输入的。
// Create a Input Layout for the MultiStream data.
// Notice that the 4th parameter is the stream index. This indicates the
// VB that the particular data comes from. In this case, position data
// comes from stream 0. Normal data comes from stream 1. Texture coordinate
// data comes from stream 2.
const D3D10_INPUT_ELEMENT_DESC vertlayout_singleindex[] =
{
{ "SV_POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
{ " NORMAL ", 0, DXGI_FORMAT_R32G32B32_FLOAT, 1, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
{ "TEXCOORD0", 0, DXGI_FORMAT_R32G32_FLOAT, 2, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
};
UINT iNumElements = sizeof(vertlayout_singleindex)/sizeof(D3D10_INPUT_ELEMENT_DESC);
D3D10_PASS_DESC PassDesc;
g_pRenderScene_SI->GetPassByIndex( 0 )->GetDesc( &PassDesc );
V_RETURN( pd3dDevice->CreateInputLayout( vertlayout_singleindex, iNumElements, PassDesc.pIAInputSignature, &g_pVertexLayout_SI ) );
Before rendering the sample must make sure that all streams are bound, that the index buffer is bound, and that the correct input layout is set. Notice that the third vertex stream ( stream number 2 ) can be switched between the original set of texture coordinates and the alternate set of texture coordinates without affecting the other streams.
在绘制前程序必须保证所有缓存都被保证……靠,又来了,烦不烦!!
UINT strides[3];
UINT offsets[3] = {0, 0, 0};
// Set the parameters for MultiIndex or SingleIndex
ID3D10Buffer* pBuffers[3];
...
pd3dDevice->IASetInputLayout( g_pVertexLayout_SI );
pBuffers[0] = g_pVBs[ST_VERTEX_POSITION];
pBuffers[1] = g_pVBs[ST_VERTEX_NORMAL];
if( g_bUseAltUV )
pBuffers[2] = g_pVBs[ST_VERTEX_TEXTUREUV2];
else
pBuffers[2] = g_pVBs[ST_VERTEX_TEXTUREUV];
strides[0] = sizeof(D3DXVECTOR3);
strides[1] = sizeof(D3DXVECTOR3);
strides[2] = sizeof(D3DXVECTOR2);
numVBsSet = 3;
...
pd3dDevice->IASetVertexBuffers( 0, numVBsSet, pBuffers, strides, offsets );
pd3dDevice->IASetIndexBuffer( g_pIB, DXGI_FORMAT_R32_UINT, 0 );
pd3dDevice->IASetPrimitiveTopology( D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST );
The Direct3D 10 codepath also allows the use of vertex streams that are stored at different frequencies. In order to access this vertex data stored at different frequencies, multiple index buffers are needed. Note that there is no API to allow users of Direct3D 10 to set more than one index buffer at a time. To perform this multi-stream multi-index rendering, some fancy shader work must be done.
D3D10允许使用不同顶点个数的顶点缓存。为了获得不同缓存顶点数据,我们需要使用多个索引缓存。注意在D3D10中没有API允许用户同时设置多个索引缓存,为了做到多数据流单索引,必须在shader里面做一些工作。
First the buffers are created. In this scenario, 4 streams are created. They are described as follows:
首先创建各个缓存。在这个场景里创建了4个流。它们描述如下:
FewVertexPosition:保存NumVertices个单独的位置
PositionIndices:在位置缓存里保存NumFaces×3个索引
VertexTextureUV:保存NumFaces×3个纹理坐标
VertexTextureUV2:保存NumFaces×3个纹理坐标
FaceNomral:保存NumFaces个顶点法向
Note that FewVertexPosition holds NumVertices positions, while PositionIndices, VertexTextureUV, and VertexTextureUV2 hold NumFaces*3 values. In addition, FaceNormals only contains NumFaces values. This shows a large difference in data frequencies since NumVertices != NumFaces*3 in this scenario.
注意FewVertexPosition保存NumVertices个位置,同时PositionIndeics,VertexTextureUV和VertexTextureUV2保存NumFaces×3个数值。FaceNormals只保存NumFaces个法向。在这个场景里,数据个数有巨大的区别,因为NumVertices不等于NumFaces*3。
Since the position indices and texture coordinates share the same frequency, they can be added to the input layout.
因为位置索引和纹理坐标使用相同的顶点个数,它们能够被添加到输入层次中。
// Create a Input Layout for the MultiStream MultiIndex data.
//
const D3D10_INPUT_ELEMENT_DESC vertlayout_multiindex[] =
{
{ "POSINDEX", 0, DXGI_FORMAT_R32_UINT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
{ "TEXCOORD0", 0, DXGI_FORMAT_R32G32_FLOAT, 1, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 },
};
iNumElements = sizeof(vertlayout_multiindex)/sizeof(D3D10_INPUT_ELEMENT_DESC);
g_pRenderScene_MI->GetPassByIndex( 0 )->GetDesc( &PassDesc );
V_RETURN( pd3dDevice->CreateInputLayout( vertlayout_multiindex, iNumElements, PassDesc.pIAInputSignature, &g_pVertexLayout_MI ) );
They are also bound as vertex buffers similarly to the way it is handled for multi-stream single-index rendering.
同时和多数据流单索引类似绑定顶点缓存。
pd3dDevice->IASetInputLayout( g_pVertexLayout_MI );
pBuffers[0] = g_pVBs[ST_POSITION_INDEX];
if( g_bUseAltUV )
pBuffers[1] = g_pVBs[ST_VERTEX_TEXTUREUV2];
else
pBuffers[1] = g_pVBs[ST_VERTEX_TEXTUREUV];
strides[0] = sizeof(UINT);
strides[1] = sizeof(D3DXVECTOR2);
However, the data stored at different frequencies must be passed in as buffers.
当然,保存不同数目顶点的数据必须作为缓存传入:
g_pPosBuffer->SetResource( g_pFewVertexPosRV );
g_pNormBuffer->SetResource( g_pFaceNormalRV );
The rest of the magic happens in the vertex shader. The vertex position is loaded from the position buffer based upon the input position index. The face normal is loaded from the normal buffer using the vertexID/3. SV_VertexID is automatically generated by the input assembler. The sample could also use SV_PrimitiveID (not divided by 3) to accomplish the same task.
魔术的剩下部分就在VS里完成。基于输入位置索引的顶点位置从位置缓存里载入,表面法向从法向缓存里载入使用vertexID/3。SV_VertexID在IA中自动生成。Sample也可以是用SV_PrimitiveID(并不是除以3)来完成相同的任务。
float4 Pos = g_posBuffer.Load( input.PositionIndex );
float4 Norm = g_normBuffer.Load( input.vertID/3 );
Particle systems are a common method for creating volumetric effects in games. They can be used to simulate various effects such as fire, cloudes, smoke, dust, glowing projectiles, magic spells, etc. A common technique is to use 2D camera-aligned quads centered at each particle to represent the volume that the particle represents. Because the quads follow the camera, they give the illusion of a substance filling a 3D volume. However, this illusion often breaks down when the 2D sprites used to visualize the particles intersect with the world geometry. The intersection of the 2D quad with the 3D world geometry creates a hard, straight line. On one side of the line is particle. On the other side is world geometry.
粒子系统是在游戏中创建体绘制效果的一种普遍采用的算法。它们能被用作模拟大量的效果,比如火、云、烟尘、光晕物体,魔法等等。一种普通的方法是在粒子中心使用和屏幕平行的2D方块代表粒子。因为方块和屏幕平行,所以它们看起来就像许多很小的3D物体。当然,这种方法在要表现2D方块(精灵)和世界上其他物体相交时会出现问题。2D方块和3D现实世界几何相交会出现一条直线,一半在粒子上,另一半在现实世界几何上。
Hard Flat Particles Example:
The first approach to dealing with this is to perturb the depth being output from the pixel shader by a depth being stored in the particle texture. This makes the intersections with the world geometry follow the contour of the particle.
Depth Particles Example:
对应这种现象的第一个方法是使用保存在粒子纹理里的深度值来扰动PS输出的深度。这使得只有粒子的外形和现实世界几何相交。
深度粒子系统的例子:
In the basic flat particle system, the line where the particle quad intersects the geometry is a giveaway that the particles are not actually 3D. To avoid this, the sample can read back the depth buffer as a texture. In the shader, this depth value is sampled and tested against the depth value being rendered for the current pixel in the particle. The alpha value increases as the difference between the depth value in the buffer and then depth being written out from the pixel shader decreases. Therefore, the particle becomes more transparent as it approaches intersection with the scene geometry.
Soft 2D Particles Example:
在基本的平面粒子系统中,粒子方块和几何相交产生的直线是由于粒子本身不是3D的。为了避免这种现象,sample能够把深度缓存作为纹理读回。在Shader中,这个深度值被采样并且和PS中正在处理的粒子的像素深度做比较。Alpha值会在采样深度值和像素深度值之差减小时增加。因此,粒子当它和几何相交时变得更加透明。
软2D粒子的例子:
The same approach can be used with particles that output depth. The only difference is that the depth compared with the depth buffer is not the depth from the quad, but the depth from the quad augemented by the depth stored in the particle texture.
Soft 2D Particles Example:
相同的方法也可以对输入深度的粒子使用。它们之间的唯一区别在于和深度缓存采样深度做比较的不是像素深度而是粒子纹理中作为参数保存的深度。
软2D粒子系统的例子:
To animate the 2D particles, the sample uses a series of animated particle texture "frames" stored in a volume texture. The first slice of the volume texture stores the first frame of the animation. The last slice stores the last. As the particle ages, the shader looks up
为了表现2D粒子动画,sample使用一系列的运动粒子纹理“帧”保存在体纹理中。体纹理的第一页保存动画的第一帧,最后一页保存最后一帧。当粒子周期改变时,shader查找不同的纹理帧。
The volumetric techniques in the sample give the particles the appearance of true 3D volume instead of the flat look of 2D particles. In order to achieve this, the sample traces rays through an imaginary 3D primitive circumscribed by the camera-aligned particle quad. In this case the sample uses a sphere as the imaginary primitive. There three main reasons for choosing a sphere. First, it is always possible to circumscribe a sphere in a camera-aligned quad. Second, a sphere is a very symmetrical shape. It looks the same no matter how you orient it. Third, the intersection between a ray and a sphere is easy to compute.
The main process is as follows. The particles are handled in the same manner as in the 2D technique until they reach the pixel shader. The pixel shader computes a ray from the eye to the pixel being drawn on the quad.
Sample里的体绘制技术产生了粒子的真实3D体而不是2D平面粒子。为了得到这种效果,sample对一个被和屏幕对齐的粒子方块限制的假想的3D物体做光线跟踪得到。在这个粒子中sample使用球形作为假想物体。首先,球可以被限定在一个2D方块中。其次,球是一个对称的图形,不管你从哪个角度看它都是相同的。第三,光线和球的相交很容易计算。
It then calculates the intersections of this ray and the imaginary sphere circumscribed by the particle quad. If no intersections occur, the ray is discarded.
然后计算球和光线的交点,如果没有交点的话,这条光线就被取消。
Using a fixed step-size, the shader determines the number of steps needed to march from one intersection point to the other. Along the way, several octaves of noise are sampled. These are stored in a 4 channel volume texture. The rgb channels contain the inverse density gradient. The alpha channel contains the density. Lighting is computed at the poing using a combination of the normal from the center of the sphere at the point and the density gradient. Each succesive step is summed with the previous step and the final result is the opacity and color value for the ray traced through the volume. This result is modified slightly by some overall opacity values in order get a specific look. E.g. increasing the g_noiseOpacity parameter will result in denser smoke.
使用修正的步数,Shader可以确定从一个交点到另一个交点匹配的步数。用这种方法,几个基于8的噪音被采样。它们被保存在体纹理的4 个通道中。Rgb同胞保存反密度梯度。每个后继步骤结果和前面的步骤累加,这样最后结果就是不透明的,代表光线从体中穿过的颜色。这个结果需要使用一些不透明参数做一些改动以得到特定的效果,比如增加g_noiseOpacity参数能够得到浓密的烟雾。
If the ray intersects the depth buffer before it exits the sphere, the shader changes the rear exit point to be the depth buffer intersection. Optionally, to achieve the soft intersection with world geometry, the alpha can be attenuated based upon the distance between the sample and the depth buffer.
如果光线在它和球面相交之前就和其他物体几何相交,shader回改变后面的射出点,使之成为深度缓存交点。为了可选择的获得和世界中其他物体几何的软相交结果,alpha值可以根据sample点和深度缓存之间的距离来衰减。
硬体粒子系统的例子:
软体粒子系统的例子:
Although the particles are animating (moving away from the source), added detail is gained by animating the texture coordinates used to calculate the lookup in the the noise volume texture. By moving the texture coordinates in one direction, the smoke it made to look like it is moving in the opposite direction.
虽然粒子是运动到(从源向外运动),但是也可以通过改变用于查找计算噪音体纹理的纹理坐标来获得附加的细节。通过向一个方向移动纹理坐标,烟雾就会看起来向另外一个方向移动。