private static RayHit Trace(LightRay lightRay, MainBakeArgs bakeArgs) { RayHit bestHit = BlankHit(); for (int i = 0; i < bakeArgs.ObjectData.Count; i++) { var objectDatum = bakeArgs.ObjectData[i]; if (!objectDatum.Bounds.IntersectsLightRay(lightRay)) { continue; } for (int j = 0; j < objectDatum.IndicesCount; j += 3) { int subIndex0 = j + objectDatum.IndicesOffset; // Going from an object's Local-Space and into World-Space requires matrix-multiplication. For this to // work, we need to Vector4s with a 'w' component that contains a value of 1.0. This value of 1.0 // allows the matrix multiplication to perfrom a transpose that will carry over into the result. It's // one of the reasons for using "w = 1.0" for positions and "w = 0.0" for directions. // -FCT Vector3 v0 = objectDatum.LocalToWorldMatrix * bakeArgs.Vertices[bakeArgs.Indices[subIndex0] + objectDatum.VerticesOffset].Position(); Vector3 v1 = objectDatum.LocalToWorldMatrix * bakeArgs.Vertices[bakeArgs.Indices[subIndex0 + 1] + objectDatum.VerticesOffset].Position(); Vector3 v2 = objectDatum.LocalToWorldMatrix * bakeArgs.Vertices[bakeArgs.Indices[subIndex0 + 2] + objectDatum.VerticesOffset].Position(); float s = float.MaxValue; Vector3 n = new Vector3(0.0f, 0.0f, 0.0f); bool theresAHit = TriangleIntersect(lightRay, v0, v1, v2, bakeArgs.LightSourcePosition, out s, out n); if (theresAHit) { if (s < bestHit.Distance) { bestHit.Distance = s; bestHit.Position = lightRay.Origin + (s * lightRay.Direction); bestHit.Normal = n; bestHit.HasAHit = true; } } } // End loop that checks all triangles for current object } // End loop that checks all objects if (bestHit.HasAHit) { bestHit.Normal = bestHit.Normal.normalized; } return(bestHit); }
private static LightRay CreateInitialLightRay(Vector2 uv, float halfSize, MainBakeArgs bakeArgs) { var intersectionPointOffset = ((uv.x * halfSize) * bakeArgs.LightSourceRightward) + ((uv.y * halfSize) * bakeArgs.LightSourceUpward) + (s_shadowFocusDistance * bakeArgs.LightSourceForward); LightRay lightRay = new LightRay() { Color = new Vector3(1.0f, 1.0f, 1.0f), Origin = bakeArgs.LightSourcePosition, Direction = intersectionPointOffset.normalized }; return(lightRay); }
private IEnumerator CookieBaking() { yield return(null); var resolution = s_resolutionOptions[s_selectedCookieResolution]; // We only want to use GameObjects that are within the outer radius that we have set. So, a quick test // to see if something is within the ball park would be to check if their bounding box insersects with // a bounding box that contains the outer radius. Once that has been done, we can take a closer look at // what's left. var meshRenders = FindObjectsOfType <MeshRenderer>(); var lightCenter = s_currentLightComponent.transform.position; var lightBoundingBox = new UnityEngine.Bounds(lightCenter, 2.0f * s_outerRadius * Vector3.one); var intersectingBounds = new List <MeshRenderer>(); foreach (var meshRenderer in meshRenders) { var otherBounds = meshRenderer.bounds; if (otherBounds.Intersects(lightBoundingBox)) { intersectingBounds.Add(meshRenderer); } } yield return(null); // Now, we can limit things a bit more by finding the point within our mesh bounds that is nearset to // the light's center. We then check this point to see if it's within the distance of our outer radius. var intersectingOuterRadius = new List <MeshRenderer>(); foreach (var meshRenderer in intersectingBounds) { var otherBounds = meshRenderer.bounds; var nearsetPointToLight = otherBounds.ClosestPoint(lightCenter); if ((nearsetPointToLight - lightCenter).sqrMagnitude <= (s_outerRadius * s_outerRadius)) { intersectingOuterRadius.Add(meshRenderer); } } yield return(null); var processMeshRenderer = intersectingBounds; var processMeshFilter = new List <MeshFilter>(); // We're reusing the List from intersectingBounds and placing it under a more appropriate name. // Basically, we're reusing a piece of memory that's no longer needed instead of allocating a new List // with a new array. Also, this array shouldn't need to be resized to something larger because it // already a capacity that is greater than or equal to the number of items we will be processing. processMeshRenderer.Clear(); intersectingBounds = null; // Also, while we're at it, lets make sure that we are only baking items that are static. After all, // what's the point of baking the shadow casting item that might not be there? foreach (var meshRender in intersectingOuterRadius) { var gameObject = meshRender.gameObject; // If the gameObject isn't static, than it can be moved. Since we are not updating the cookie at // runtime, having a moving object would be bad. if (!gameObject.isStatic) { continue; } // The mesh for our object is inside the MeshFilter, so we need that to get the mesh. Also, since we're // dealing with static meshes, then we really shouldn't be dealing with any Skinned Mesh Renderers. var meshFilter = gameObject.GetComponent <MeshFilter>(); if (meshFilter == null) { continue; } // If there's no mesh, then what are we even bothering with this? if (meshFilter.sharedMesh == null) { continue; } processMeshRenderer.Add(meshRender); processMeshFilter.Add(meshFilter); } yield return(null); // Todo: With this design, items that use the same mesh will replicate the mesh verts and triangle index // arrays. I need to add a way to check if we have already added a mesh and if so, pull up the triangle // index array information to pass along to the meshRefDatum. var meshObjecRefData = new List <MeshObject>(processMeshRenderer.Count); var indexList = new List <int>(); var vertexList = new List <Vector3>(processMeshRenderer.Count * 50); for (int i = 0; i < processMeshRenderer.Count; i++) { var mesh = processMeshFilter[i].sharedMesh; var meshVerts = mesh.vertices; var meshTriangleIndices = mesh.triangles; var meshRefDatum = new MeshObject() { LocalToWorldMatrix = processMeshRenderer[i].localToWorldMatrix, IndicesOffset = indexList.Count, // The starting index of the vert-index for this object's mesh. IndicesCount = meshTriangleIndices.Length, // The number of indices used to form all of the mesh triangles. VerticesOffset = vertexList.Count, Bounds = new Bounds(processMeshRenderer[i].bounds) }; meshObjecRefData.Add(meshRefDatum); vertexList.AddRange(meshVerts); indexList.AddRange(meshTriangleIndices); } yield return(null); var threadArgs = new MainBakeArgs() { ObjectData = meshObjecRefData, Vertices = vertexList, Indices = indexList, ImageResolution = resolution, LightSourcePosition = lightCenter, LightSourceForward = s_currentLightComponent.transform.forward, LightSourceUpward = s_currentLightComponent.transform.up, LightSourceRightward = s_currentLightComponent.transform.right, LightSourceTheata = Mathf.Deg2Rad * s_currentLightComponent.spotAngle / 2.0f }; using (var mainBakeThread = new BackgroundWorker()) { mainBakeThread.DoWork += MainBakeThread_DoWork; yield return(null); mainBakeThread.RunWorkerAsync(threadArgs); s_currentBakeState = BakeState.Bake; yield return(null); while (!threadArgs.Complete) { yield return(new EditorWaitForSeconds(0.2f)); } } s_currentBakeState = BakeState.Finalize; yield return(null); /// /// Code for saving our results into the project and applying them to the light component. /// // Create a Texture2D to place our results into. This Texture2D will be added to the project's assets // and it will be applied to the light source we were raytracing. Texture2D finalResults = new Texture2D(resolution, resolution, TextureFormat.RGBAFloat, false, true) { alphaIsTransparency = true, filterMode = FilterMode.Point, wrapMode = TextureWrapMode.Clamp, name = s_currentLightComponent.name + "_ShadowCookie_" + resolution.ToString() }; yield return(null); finalResults.SetPixels(threadArgs.Result); yield return(null); // Check for the location where we will be saving the Texture2D. If that location doesn't exist, then // create it. var assetFolderPath = "Assets/Light_Cookies/" + s_currentLightComponent.gameObject.scene.name; if (!Directory.Exists(assetFolderPath)) { if (!Directory.Exists("Assets/Light_Cookies")) { AssetDatabase.CreateFolder("Assets", "Light_Cookies"); yield return(null); } AssetDatabase.CreateFolder("Assets/Light_Cookies", s_currentLightComponent.gameObject.scene.name); } yield return(null); // Save the Texture2D as a project asset. var assetPath = assetFolderPath + "/" + finalResults.name + ".asset"; AssetDatabase.CreateAsset(finalResults, assetPath); yield return(null); // Apply the Texture2D to the light as a cooke, then finish cleaning up. s_currentLightComponent.cookie = finalResults; EditorUtility.SetDirty(s_currentLightComponent.gameObject); s_bakingCoroutine = null; // The coroutines for MonoBehaviors have the option to manipulate them from the outside, which I have // made use of. With a bit of luck, those functions will be added to the EditorCoroutines at some point // in time. // -FCT yield return(null); s_currentBakeState = BakeState.SettingSelection; }