// -------------------------------------------------------------------------------------------- // // Lights // -------------------------------------------------------------------------------------------- // // Returns lights in "srgb-ish" color space. // Note that light values may be > 1, which is not really kosher in sRGB static void BuildLights(SceneStatePayload payload) { var lightPayload = payload.lights; lightPayload.ambientColor = RenderSettings.ambientLight; for (int i = 0; i < App.Scene.GetNumLights(); ++i) { var transform = App.Scene.GetLight(i).transform; Light unityLight = transform.GetComponent <Light>(); Debug.Assert(unityLight != null); Color lightColor = unityLight.color * unityLight.intensity; lightColor.a = 1.0f; // No use for alpha with light color. // because transform.name might not be unique int uniquifyingId = payload.idGenerator.GetIdFromInstanceId(transform); // Match the old gltf export logic for easier diffing var transformNameNoSpaces = transform.name.Replace(" ", "_"); lightPayload.lights.Add(new ExportUtils.LightPayload { legacyUniqueName = $"{transformNameNoSpaces}_i{uniquifyingId}", name = transformNameNoSpaces, type = unityLight.type, lightColor = lightColor, xform = ExportUtils.ChangeBasis(transform, payload) }); } }
// widget.ReferenceImage must be != null // Never returns null // id is an instance id static ImageQuadPayload BuildImageQuadPayload( SceneStatePayload payload, ImageWidget widget, DynamicExportableMaterial material, int id) { string nodeName = $"image_{widget.GetExportName()}_{id}"; // The child has no T or R but has some non-uniform S that we need to preserve. // I'm going to do this by baking it into the GeometryPool GameObject widgetChild = widget.m_ImageQuad.gameObject; Matrix4x4 xform = ExportUtils.ChangeBasis(widget.transform, payload); Vector3 localScale = widgetChild.transform.localScale; // localScale is a muddle of aspect ratio and overall size. We don't know which of x or y // was modified to set the aspect ratio, so get the size from z. if (localScale.z > 0) { float overallSize = localScale.z; localScale /= overallSize; xform = xform * Matrix4x4.Scale(Vector3.one * overallSize); } // Create pool and bake in the leftover non-uniform scale GeometryPool pool = new GeometryPool(); pool.Layout = material.VertexLayout; pool.Append(widgetChild.GetComponent <MeshFilter>().sharedMesh, fallbackColor: Color.white); pool.ApplyTransform( Matrix4x4.Scale(localScale), Matrix4x4.identity, 1f, 0, pool.NumVerts); // Important: This transform should only be applied once, since the pools are shared, and // cannot include any mesh-local transformations. ExportUtils.ConvertUnitsAndChangeBasis(pool, payload); if (payload.reverseWinding) { // Note: this triangle flip intentionally un-does the Unity FBX import flip. ExportUtils.ReverseTriangleWinding(pool, 1, 2); } return(new ExportUtils.ImageQuadPayload(payload.groupIdMapping.GetId(widget.Group)) { // because Count always increments, this is guaranteed unique even if two different images // have the same export name legacyUniqueName = $"refimage_i{payload.imageQuads.Count}", // We could (but don't) share the same aspect-ratio'd-quad for all instances of a refimage. // Since the mesh is unique to the node, it can have the same name as the node. geometryName = nodeName, nodeName = nodeName, xform = xform, geometry = pool, exportableMaterial = material, }); }
// -------------------------------------------------------------------------------------------- // // Xform only, no mesh data // -------------------------------------------------------------------------------------------- // static void BuildEmptyXforms(SceneStatePayload payload, IEnumerable <MediaWidget> widgets) { foreach (var mediaWidget in widgets) { payload.referenceThings.Add(new ExportUtils.XformPayload( payload.groupIdMapping.GetId(mediaWidget.Group)) { name = mediaWidget.GetExportName(), xform = ExportUtils.ChangeBasis(mediaWidget.transform, payload) }); } }
public void Serialize() { // Get transform in canvas space, and write to scene. TrTransform xf_CS = Coords.AsCanvas[transform]; var basisMat = Matrix4x4.identity; if (m_UnitsInMeters) { basisMat[0, 0] *= App.UNITS_TO_METERS; basisMat[1, 1] *= App.UNITS_TO_METERS; basisMat[2, 2] *= -1 * App.UNITS_TO_METERS; } m_UsdCamera.transform = ExportUtils.ChangeBasis(xf_CS.ToMatrix4x4(), basisMat, basisMat.inverse); m_UsdCamera.fov = m_RecordingCamera.fieldOfView; m_Scene.Write(m_xformName, m_UsdCamera); }
// Unused and untested #if false /// Converts the existing payload to the new color space and vector basis. /// Note that scale is never converted and only gamma -> linear is currently supported. public static void ConvertPayload(ColorSpace newColorSpace, Vector3[] newVectorBasis, SceneStatePayload payload) { // We don't handle uninitialized color spaces or linear -> srgb conversions. // This is just not currently part of the export logic, so it's not implemented here. if (newColorSpace == ColorSpace.Uninitialized || payload.colorSpace == ColorSpace.Uninitialized || (payload.colorSpace == ColorSpace.Linear && newColorSpace == ColorSpace.Gamma)) { throw new NotImplementedException(); } if (newColorSpace != payload.colorSpace) { ExportUtils.ConvertToLinearColorspace(payload.env); ExportUtils.ConvertToLinearColorspace(payload.lights); foreach (var group in payload.groups) { ExportUtils.ConvertToLinearColorspace(group.brushMeshes); } ExportUtils.ConvertToLinearColorspace(payload.modelMeshes); } if (newVectorBasis[0] != payload.vectorBasis[0] || newVectorBasis[1] != payload.vectorBasis[1] || newVectorBasis[2] != payload.vectorBasis[2]) { // Never apply a scale change when changing basis. Matrix4x4 basis = ExportUtils.GetBasisMatrix(payload); foreach (var group in payload.groups) { ExportUtils.ChangeBasis(group.brushMeshes, basis); } ExportUtils.ChangeBasis(payload.modelMeshes, basis); ExportUtils.ChangeBasis(payload.referenceImages, basis); ExportUtils.ChangeBasis(payload.referenceModels, basis); } payload.colorSpace = newColorSpace; payload.vectorBasis = newVectorBasis; }
/// Plays back a named transform onto the current transform from a usd path. /// The transform can optionally be smoothed using exponential smoothing. /// Smoothing will be clamped between 0 - 1. public void StartPlayback(string sketchName = "/Sketch", string xformName = "/VideoCamera", float smoothing = 0) { m_xformName = xformName; m_Smoothing = Mathf.Clamp01(smoothing); m_IsRecording = false; // Older versions of Tilt Brush exported usda camera paths in decimeters. We now // export in meters to match USD geometry export. Older versions also didn't export any sketch // data so we check here for the presence of sketch data to decide how to treat the camera // path units. bool hasSketchRoot = m_Scene.Stage.GetPrimAtPath(new pxr.SdfPath(sketchName)); m_xformName = hasSketchRoot ? sketchName + xformName : xformName; float scale = hasSketchRoot ? App.UNITS_TO_METERS : 1; m_UnitsInMeters = hasSketchRoot; m_Scene.Time = null; m_UsdCameraInfo = new UsdCameraSample(); m_UsdCameraInfo.shutter = new USD.NET.Unity.CameraSample.Shutter(); m_Scene.Read(m_xformName, m_UsdCameraInfo); m_UsdCamera = new UsdCameraXformSample(); m_Scene.Time = 0; m_Scene.Read(m_xformName, m_UsdCamera); var basisMat = AxisConvention.GetFromUnity(AxisConvention.kUsd) * Matrix4x4.Scale(Vector3.one * scale); m_UsdCamera.transform = ExportUtils.ChangeBasis(m_UsdCamera.transform, basisMat, basisMat.inverse); TrTransform xf_WS = UsdXformToWorldSpaceXform(m_UsdCamera); xf_WS.ToTransform(transform); m_PlaybackCameras = FindObjectsOfType <Camera>(); }
public void Deserialize() { m_Scene.Read(m_xformName, m_UsdCamera); var basisMat = Matrix4x4.identity; if (m_UnitsInMeters) { basisMat[0, 0] *= App.METERS_TO_UNITS; basisMat[1, 1] *= App.METERS_TO_UNITS; basisMat[2, 2] *= -1 * App.METERS_TO_UNITS; } m_UsdCamera.transform = ExportUtils.ChangeBasis(m_UsdCamera.transform, basisMat, basisMat.inverse); TrTransform xf_WS = UsdXformToWorldSpaceXform(m_UsdCamera); TrTransform old_WS = TrTransform.FromTransform(transform); TrTransform new_WS = TrTransform.Lerp(old_WS, xf_WS, 1 - m_Smoothing); new_WS.scale = m_UsdCameraInfo.eyeScale != 0 ? m_UsdCameraInfo.eyeScale : 1; new_WS.ToTransform(transform); // Pre-M23.3, .usd files won't have fov defined, so this value will be negative. if (m_UsdCamera.fov > 0) { // A bit brute force, but we're running through all cameras in the scene to make // sure the preview shows the modified fov. for (int i = 0; i < m_PlaybackCameras.Length; ++i) { if (m_PlaybackCameras[i].gameObject.activeInHierarchy && m_PlaybackCameras[i].isActiveAndEnabled) { m_PlaybackCameras[i].fieldOfView = m_UsdCamera.fov; } } } }
private static void BuildModelsAsModelMeshes( SceneStatePayload payload, IEnumerable <ModelWidget> modelWidgets) { // This is a quadruple-nested loop that's triply flattened into a single IEnumerable. // The structure of the data is is: // 1. There are 1+ Models (which I'll call "prefab" because Model is a loaded term here) // 2. Each "prefab" is used by 1+ ModelWidgets ("instance") // 3. Each ModelWidget, and the "prefab", contains 1+ GameObjects (which has a single Mesh) // 4. Each Mesh has 1+ unity submeshes (almost always exactly 1) // These are converted to ModelMeshPayload // // The resulting ModelMeshPayloads are a mixture of data from all 4 levels, for example // - ModelMeshPayload.model comes from level 1 // - ModelMeshPayload.modelId comes from level 2 // - ModelMeshPayload.xform comes from level 3 // - ModelMeshPayload.pool comes from level 4 // // Orthogonal to this nesting, some data comes from the "prefab" and some from the "instance", // since we don't want to have to convert a mesh into GeometryPool once per instance. // So we iterate level 3 for the Model once, up front; then each ModelWidget level 3 is // a parallel iteration with that Model's pre-computed iteration. foreach (var group in modelWidgets.GroupBy(widget => widget.Model)) { Model prefab = group.Key; // Each element represents a GameObject in the "prefab" List <PrefabGeometry[]> prefabObjs = GetPrefabGameObjs(payload, prefab).ToList(); foreach ((ModelWidget widget, int widgetIndex) in group.WithIndex()) { Matrix4x4 widgetXform = ExportUtils.ChangeBasis(widget.transform, payload); // Acts like our AsScene[], AsCanvas[] accessors var AsWidget = new TransformExtensions.RelativeAccessor(widget.transform); // Verify that it's okay to parallel-iterate over the prefab and instance gameObjs. // It should be, unless one or the other has mucked with their m_MeshChildren. MeshFilter[] instanceObjs = widget.GetMeshes(); if (prefabObjs.Count != instanceObjs.Length) { Debug.LogError($"Bad Model instance: {prefabObjs.Count} {instanceObjs.Length}"); // TODO: check order as well, somehow continue; } // Parallel iterate, taking shared things (GeometryPool, material, name) from the // "prefab" and xforms, unique ids, etc from the "instance". for (int objIndex = 0; objIndex < prefabObjs.Count; ++objIndex) { PrefabGeometry[] prefabSubMeshes = prefabObjs[objIndex]; GameObject instanceObj = instanceObjs[objIndex].gameObject; Matrix4x4 localXform = ExportUtils.ChangeBasis(AsWidget[instanceObj.transform], payload); int objId = payload.idGenerator.GetIdFromInstanceId(instanceObj); Matrix4x4 xform = ExportUtils.ChangeBasis(instanceObj.transform, payload); foreach (PrefabGeometry prefabSubMesh in prefabSubMeshes) { payload.modelMeshes.Add(new ExportUtils.ModelMeshPayload( payload.groupIdMapping.GetId(widget.Group)) { // Copied from "prefab" model = prefabSubMesh.model, geometry = prefabSubMesh.pool, exportableMaterial = prefabSubMesh.exportableMaterial, geometryName = prefabSubMesh.name, // Unique to instance legacyUniqueName = $"{prefabSubMesh.name}_i{objId}", nodeName = $"{prefabSubMesh.name}_{widgetIndex}", parentXform = widgetXform, localXform = localXform, modelId = widgetIndex, xform = xform, }); } } } } }