/// <summary> /// Generates the indexes required to render a shadow volume into the /// index buffer which is passed in, and updates shadow renderables to use it. /// </summary> /// <param name="edgeData">The edge information to use.</param> /// <param name="indexBuffer">The buffer into which to write data into; current /// contents are assumed to be discardable.</param> /// <param name="light">The light, mainly for type info as silhouette calculations /// should already have been done in <see cref="UpdateEdgeListLightFacing"/></param> /// <param name="shadowRenderables">A list of shadow renderables which has /// already been constructed but will need populating with details of /// the index ranges to be used.</param> /// <param name="flags">Additional controller flags, see <see cref="ShadowRenderableFlags"/>.</param> protected virtual void GenerateShadowVolume(EdgeData edgeData, HardwareIndexBuffer indexBuffer, Light light, ShadowRenderableList shadowRenderables, int flags) { // Edge groups should be 1:1 with shadow renderables Debug.Assert(edgeData.edgeGroups.Count == shadowRenderables.Count); LightType lightType = light.Type; bool extrudeToInfinity = (flags & (int)ShadowRenderableFlags.ExtrudeToInfinity) > 0; // Lock index buffer for writing IntPtr idxPtr = indexBuffer.Lock(BufferLocking.Discard); int indexStart = 0; unsafe { // TODO: Will currently cause an overflow for 32 bit indices, revisit short *pIdx = (short *)idxPtr.ToPointer(); int count = 0; // Iterate over the groups and form renderables for each based on their // lightFacing for (int groupCount = 0; groupCount < edgeData.edgeGroups.Count; groupCount++) { EdgeData.EdgeGroup eg = (EdgeData.EdgeGroup)edgeData.edgeGroups[groupCount]; ShadowRenderable si = (ShadowRenderable)shadowRenderables[groupCount]; RenderOperation lightShadOp = null; // Initialise the index bounds for this shadow renderable RenderOperation shadOp = si.GetRenderOperationForUpdate(); shadOp.indexData.indexCount = 0; shadOp.indexData.indexStart = indexStart; // original number of verts (without extruded copy) int originalVertexCount = eg.vertexData.vertexCount; bool firstDarkCapTri = true; int darkCapStart = 0; for (int edgeCount = 0; edgeCount < eg.edges.Count; edgeCount++) { EdgeData.Edge edge = (EdgeData.Edge)eg.edges[edgeCount]; EdgeData.Triangle t1 = (EdgeData.Triangle)edgeData.triangles[edge.triIndex[0]]; EdgeData.Triangle t2 = edge.isDegenerate ? (EdgeData.Triangle)edgeData.triangles[edge.triIndex[0]] : (EdgeData.Triangle)edgeData.triangles[edge.triIndex[1]]; if (t1.lightFacing && (edge.isDegenerate || !t2.lightFacing)) { /* Silhouette edge, first tri facing the light * Also covers degenerate tris where only tri 1 is valid * Remember verts run anticlockwise along the edge from * tri 0 so to point shadow volume tris outward, light cap * indexes have to be backwards * * We emit 2 tris if light is a point light, 1 if light * is directional, because directional lights cause all * points to converge to a single point at infinity. * * First side tri = near1, near0, far0 * Second tri = far0, far1, near1 * * 'far' indexes are 'near' index + originalVertexCount * because 'far' verts are in the second half of the * buffer */ pIdx[count++] = (short)edge.vertIndex[1]; pIdx[count++] = (short)edge.vertIndex[0]; pIdx[count++] = (short)(edge.vertIndex[0] + originalVertexCount); shadOp.indexData.indexCount += 3; if (!(lightType == LightType.Directional && extrudeToInfinity)) { // additional tri to make quad pIdx[count++] = (short)(edge.vertIndex[0] + originalVertexCount); pIdx[count++] = (short)(edge.vertIndex[1] + originalVertexCount); pIdx[count++] = (short)edge.vertIndex[1]; shadOp.indexData.indexCount += 3; } // Do dark cap tri // Use McGuire et al method, a triangle fan covering all silhouette // edges and one point (taken from the initial tri) if ((flags & (int)ShadowRenderableFlags.IncludeDarkCap) > 0) { if (firstDarkCapTri) { darkCapStart = edge.vertIndex[0] + originalVertexCount; firstDarkCapTri = false; } else { pIdx[count++] = (short)darkCapStart; pIdx[count++] = (short)(edge.vertIndex[1] + originalVertexCount); pIdx[count++] = (short)(edge.vertIndex[0] + originalVertexCount); shadOp.indexData.indexCount += 3; } } } else if (!t1.lightFacing && (edge.isDegenerate || t2.lightFacing)) { // Silhouette edge, second tri facing the light // Note edge indexes inverse of when t1 is light facing pIdx[count++] = (short)edge.vertIndex[0]; pIdx[count++] = (short)edge.vertIndex[1]; pIdx[count++] = (short)(edge.vertIndex[1] + originalVertexCount); shadOp.indexData.indexCount += 3; if (!(lightType == LightType.Directional && extrudeToInfinity)) { // additional tri to make quad pIdx[count++] = (short)(edge.vertIndex[1] + originalVertexCount); pIdx[count++] = (short)(edge.vertIndex[0] + originalVertexCount); pIdx[count++] = (short)edge.vertIndex[0]; shadOp.indexData.indexCount += 3; } // Do dark cap tri // Use McGuire et al method, a triangle fan covering all silhouette // edges and one point (taken from the initial tri) if ((flags & (int)ShadowRenderableFlags.IncludeDarkCap) > 0) { if (firstDarkCapTri) { darkCapStart = edge.vertIndex[1] + originalVertexCount; firstDarkCapTri = false; } else { pIdx[count++] = (short)darkCapStart; pIdx[count++] = (short)(edge.vertIndex[0] + originalVertexCount); pIdx[count++] = (short)(edge.vertIndex[1] + originalVertexCount); shadOp.indexData.indexCount += 3; } } } } // Do light cap if ((flags & (int)ShadowRenderableFlags.IncludeLightCap) > 0) { ShadowRenderable lightCapRend = null; if (si.IsLightCapSeperate) { // separate light cap lightCapRend = si.LightCapRenderable; lightShadOp = lightCapRend.GetRenderOperationForUpdate(); lightShadOp.indexData.indexCount = 0; // start indexes after the current total // NB we don't update the total here since that's done below lightShadOp.indexData.indexStart = indexStart + shadOp.indexData.indexCount; } for (int triCount = 0; triCount < edgeData.triangles.Count; triCount++) { EdgeData.Triangle t = (EdgeData.Triangle)edgeData.triangles[triCount]; // Light facing, and vertex set matches if (t.lightFacing && t.vertexSet == eg.vertexSet) { pIdx[count++] = (short)t.vertIndex[0]; pIdx[count++] = (short)t.vertIndex[1]; pIdx[count++] = (short)t.vertIndex[2]; if (lightShadOp != null) { lightShadOp.indexData.indexCount += 3; } else { shadOp.indexData.indexCount += 3; } } } } // update next indexStart (all renderables sharing the buffer) indexStart += shadOp.indexData.indexCount; // add on the light cap too if (lightShadOp != null) { indexStart += lightShadOp.indexData.indexCount; } } } // Unlock index buffer indexBuffer.Unlock(); Debug.Assert(indexStart <= indexBuffer.IndexCount, "Index buffer overrun while generating shadow volume!"); }