private void OnEnable() { _isInitialized = false; if (_material == null) { _material = Resources.Load("Materials/SS_Shadow Multiply", typeof(Material)) as Material; if (_material == null) { Debug.LogError("Standard \"Materials/SS_Shadow Multiply\" material was not found. Please assign a shadow material manually for shadow to work.", this); return; } } if (_layerMask == 0) { Debug.LogError("LayerMask is empty! Shadow won't cast on any surface", this); return; } UpdateComponents(); UpdateProperties(); _isGameObjectActivePrev = SS_Extensions.IsActiveInHierarchy(gameObject); _isFirstCalculation = true; RegisterShadow(); _isInitialized = true; }
private void Start() { if (!SS_GUILayout.IsRuntimePlatformMobile()) { SS_Extensions.SetActive(gameObject, false); } }
private void OnGUI() { if (!visible) { return; } const float initWidth = 275f; float initHeight = 70f; const float initItemHeight = 30f; SS_GUILayout.itemWidth = initWidth - 20f; SS_GUILayout.itemHeight = initItemHeight; SS_GUILayout.yPos = 0f; SS_GUILayout.hovered = false; if (SS_GUILayout.IsRuntimePlatformMobile()) { SS_GUILayout.UpdateScaleMobile(); } else { SS_GUILayout.UpdateScaleDesktop(initHeight + 30f); } string levelName = Application.loadedLevelName; string sceneName = string.Empty; string text = string.Empty; switch (levelName) { case "SS_DiscoLights": sceneName = "Disco Lights Scene"; text = " This is an example of a more creative use of Swift Shadows, with hundreds " + "of shadow objects calculated and drawn at the same. All light spots " + "use a special \"light\" material and are rendered within a single draw call."; break; case "SS_Helicopter": sceneName = "Animated Shadows Scene"; text = " This scene demonstrates shadows following the transform of object they " + "are attached to. In this scene, the blades and body of helicopter, as " + "well as the platform and dish of the radar station, are actually two " + "different overlayed shadows. Nevertheless, the shadows are still drawn in a single " + "draw call by using a texture atlas."; break; case "SS_MovingCubes": sceneName = "Moving Cubes Scene"; text = " This is a generic demonstration of what Swift Shadows can do.\n" + " A raycast from the light source to the cubes determines the position of shadow, " + "and a projected quad is rendered in place. All shadows are drawn with a single " + "draw call, which is especially useful for mobile devices.\n" + " You can also notice the limitation: shadows are casted on one surface at time and " + "can extend beyond the surface they are projected on. Shadows can be set to fade away " + "as they fall at extreme angles to make this effect less noticeable."; break; case "SS_MultipleShadows": sceneName = "Multiple Shadows Demo"; text = " Multiple shadows components can be set for a single object. " + "There are 4 light sources in this scene, each resulting in a separate shadow of the object."; break; case "SS_TerrainInteraction": initHeight += 25f; sceneName = "Terrain Interaction Demo"; text = " This demonstrates how Swift Shadows can be used on uneven surfaces like terrain.\n" + " \"Use two cameras\" enables drawing the character and shadow on a separate pass, " + "which eliminates the effect of shadow being clipped with terrain. This comes with the cost " + "of some possible artifacts that aren't noticeable for most situations.\n"; break; } if (levelName != "SS_TerrainInteraction") { if (SS_GUILayout.IsRuntimePlatformMobile()) { text += "\n You can rotate the camera with two fingers.\n"; } else { if (levelName == "SS_DiscoLights") { text += "\n You can rotate the camera by holding the right mouse button.\n"; } else { text += "\n You can rotate the camera by holding the right mouse button and zoom with mouse wheel.\n"; } } } foreach (SS_ShadowMeshManager meshManager in SS_ShadowManager.Instance.ShadowManagers) { text += string.Format((meshManager.IsStatic ? "\nTotal shadows (static): " : "\nTotal shadows: ") + "{0}, drawn: {1}", meshManager.ShadowsCount, meshManager.VisibleShadowsCount); } var centeredStyle = GUI.skin.label; centeredStyle.alignment = TextAnchor.MiddleLeft; float textHeight = centeredStyle.CalcHeight(new GUIContent(text, ""), SS_GUILayout.itemWidth); initHeight += textHeight; GUI.BeginGroup(new Rect(15f, 15f, initWidth, initHeight), sceneName, GUI.skin.window); SS_GUILayout.yPos = 20f; SS_GUILayout.itemHeight = centeredStyle.CalcHeight(new GUIContent(text, ""), SS_GUILayout.itemWidth); SS_GUILayout.Label(text); SS_GUILayout.itemHeight = initItemHeight; if (levelName == "SS_TerrainInteraction" && TerrainDemoPrimaryCamera != null) { bool changed; _terrainDemoSecondaryCameraOn = SS_GUILayout.Toggle(_terrainDemoSecondaryCameraOn, "Use two cameras", out changed); if (changed) { if (_terrainDemoSecondaryCameraOn) { SS_Extensions.SetActive(TerrainDemoSecondaryCamera.gameObject, true); TerrainDemoPrimaryCamera.cullingMask = _terrainDemoPrimaryCameraLayersDefault; } else { SS_Extensions.SetActive(TerrainDemoSecondaryCamera.gameObject, false); TerrainDemoPrimaryCamera.cullingMask = TerrainDemoPrimaryCameraLayers; } SS_ShadowManager.Instance.UpdateCameraEvents(TerrainDemoSecondaryCamera); } } GUI.color = new Color(1f, 0.6f, 0.6f, 1f); if (GUI.Button(new Rect(SS_GUILayout.paddingLeft, initHeight - 40f, SS_GUILayout.itemWidth, 30f), "Back to Main Menu")) { SS_CameraFade.StartAlphaFade(Color.black, false, 0.5f, 0f, () => Application.LoadLevel("SS_Menu")); } GUI.EndGroup(); GUI.color = Color.white; SS_GUILayout.DrawLogo(Logo); }
public RecalculateShadowResult RecalculateShadow(Plane[] frustumPlanes, bool force) { _isVisible = _isStatic; // Determine whether the owner GameObject changed the active state // and react correspondingly bool isGameObjectActive = SS_Extensions.IsActiveInHierarchy(gameObject); if (isGameObjectActive != _isGameObjectActivePrev) { _isGameObjectActivePrev = isGameObjectActive; if (isGameObjectActive) { RegisterShadow(); return(RecalculateShadowResult.ChangedManager); } else { UnregisterShadow(); return(RecalculateShadowResult.ChangedManager); } } _isGameObjectActivePrev = isGameObjectActive; if (!isGameObjectActive) { return(RecalculateShadowResult.Skipped); } // Updating the transform state (position and forward vectors) // Determine whether the transform has moved Vector3 transformPosition = _transform.position; Vector3 transformForward = _transform.forward; bool transformChanged = false; if (_autoStaticTime > 0) { if (transformPosition.x != _transformPositionPrev.x || transformPosition.y != _transformPositionPrev.y || transformPosition.z != _transformPositionPrev.z || transformForward.x != _transformForwardPrev.x || transformForward.y != _transformForwardPrev.y || transformForward.z != _transformForwardPrev.z ) { _autoStaticTimeCounter = 0f; transformChanged = true; } _transformPositionPrev = transformPosition; _transformForwardPrev = transformForward; } if (!_isFirstCalculation) { // If we have AutoStatic if (_autoStaticTime > 0f) { // If the object has moved - remove the shadow // from static manager and move to non-static if (_isStatic && transformChanged) { UnregisterShadow(); _isStatic = false; RegisterShadow(); return(RecalculateShadowResult.ChangedManager); } // If the object hasn't moved for AutoStaticTime seconds, // then mark it as static _autoStaticTimeCounter += Time.deltaTime; if (!_isStatic && _autoStaticTimeCounter > _autoStaticTime) { UnregisterShadow(); _isStatic = true; RegisterShadow(); return(RecalculateShadowResult.ChangedManager); } } // Do not update static shadows by default if (_isStatic && !force) { return(RecalculateShadowResult.Skipped); } // Return if the time hasn't come yet if (_frameSkip != 0) { if (_frameSkipCounter < _frameSkip) { _frameSkipCounter++; return(RecalculateShadowResult.Skipped); } _frameSkipCounter = 0; } } // Is this our first update? _isFirstCalculation = false; // Determine the light source position bool useLightSource = _lightVectorSource == LightVectorSourceEnum.GameObject && _lightSourceObject != null; Vector3 lightSourcePosition = useLightSource ? _lightSourceObject.transform.position : new Vector3(); // The actual light direction vector that'll be used Vector3 actualLightVector; if (_lightVectorSource == LightVectorSourceEnum.GameObject && _lightSourceObject != null) { if (_lightSourceObjectIsDirectionalLight) { actualLightVector = _lightSourceObject.rotation * Vector3.forward; } else { actualLightVector.x = transformPosition.x - lightSourcePosition.x; actualLightVector.y = transformPosition.y - lightSourcePosition.y; actualLightVector.z = transformPosition.z - lightSourcePosition.z; actualLightVector = actualLightVector.FastNormalized(); } } else { actualLightVector = _lightVector; } _actualLightVectorPrev = actualLightVector; // Do a raycast from transform.position to the center of the shadow RaycastHit hitInfo; bool raycastResult = Physics.Raycast(transformPosition, actualLightVector, out hitInfo, _projectionDistance, _layerMask); if (raycastResult) { // Scale the shadow respectively Vector3 lossyScale = transform.lossyScale; float scaledDoubleShadowSize = Mathf.Max(Mathf.Max(lossyScale.x, lossyScale.y), lossyScale.z) * ShadowSize; if (!_isStatic && _cullInvisible) { // We can calculate approximate bounds for orthographic shadows easily // and cull shadows based on these bounds and camera frustum if (!_isPerspectiveProjection) { Bounds bounds = new Bounds(hitInfo.point, new Vector3(scaledDoubleShadowSize, scaledDoubleShadowSize, scaledDoubleShadowSize)); _isVisible = GeometryUtility.TestPlanesAABB(frustumPlanes, bounds); if (!_isVisible) { return(RecalculateShadowResult.Skipped); } } else { // For perspective shadows, we can at least try to // not draw shadows that fall on invisible objects Transform hitTransform = hitInfo.collider.transform; if (frustumPlanes != null) { Renderer hitRenderer = hitTransform != null ? hitTransform.renderer : null; if (hitRenderer != null) { _isVisible = GeometryUtility.TestPlanesAABB(frustumPlanes, hitRenderer.bounds); if (!_isVisible) { return(RecalculateShadowResult.Skipped); } } } } } // Calculate angle from light direction vector to surface normal _normal = hitInfo.normal; float angleToNormal = SS_Math.FastAcos(-Vector3.Dot(actualLightVector, _normal)) * Mathf.Rad2Deg; if (angleToNormal > _angleFadeMax) { // Skip shadows that fall with extreme angles _isVisible = false; return(RecalculateShadowResult.Skipped); } // Determine the forward direction of shadow base quad Vector3 forward; float dot = Vector3.Dot(transformForward, actualLightVector); if (Mathf.Abs(dot) < 1f - Vector3.kEpsilon) { forward = (transformForward - dot * actualLightVector).FastNormalized(); } else { // If the forward direction matches the light direction vector somehow Vector3 transformUp = _transform.up; forward = (transformUp - Vector3.Dot(transformUp, actualLightVector) * actualLightVector).FastNormalized(); } // Rotation of shadow base quad Quaternion rotation = Quaternion.LookRotation(forward, -actualLightVector); // Optimized version of // Vector3 right = rotation * Vector3.right; float num2 = rotation.y * 2f; float num3 = rotation.z * 2f; float num5 = rotation.y * num2; float num6 = rotation.z * num3; float num7 = rotation.x * num2; float num8 = rotation.x * num3; float num11 = rotation.w * num2; float num12 = rotation.w * num3; Vector3 right; right.x = 1f - (num5 + num6); right.y = num7 + num12; right.z = num8 - num11; // Base vertices calculation float scaledShadowSize = scaledDoubleShadowSize * 0.5f; float aspectRatioInv = 1f / _aspectRatio; Vector3 diff; diff.x = (forward.x - right.x * aspectRatioInv) * scaledShadowSize; diff.y = (forward.y - right.y * aspectRatioInv) * scaledShadowSize; diff.z = (forward.z - right.z * aspectRatioInv) * scaledShadowSize; Vector3 sum; sum.x = (forward.x + right.x * aspectRatioInv) * scaledShadowSize; sum.y = (forward.y + right.y * aspectRatioInv) * scaledShadowSize; sum.z = (forward.z + right.z * aspectRatioInv) * scaledShadowSize; Vector3 baseVertex; baseVertex.x = transformPosition.x - sum.x; baseVertex.y = transformPosition.y - sum.y; baseVertex.z = transformPosition.z - sum.z; _baseVertices[0] = baseVertex; baseVertex.x = transformPosition.x + diff.x; baseVertex.y = transformPosition.y + diff.y; baseVertex.z = transformPosition.z + diff.z; _baseVertices[1] = baseVertex; baseVertex.x = transformPosition.x + sum.x; baseVertex.y = transformPosition.y + sum.y; baseVertex.z = transformPosition.z + sum.z; _baseVertices[2] = baseVertex; baseVertex.x = transformPosition.x - diff.x; baseVertex.y = transformPosition.y - diff.y; baseVertex.z = transformPosition.z - diff.z; _baseVertices[3] = baseVertex; // Calculate a plane from normal and position SS_Plane shadowPlane = new SS_Plane(); shadowPlane.SetNormalAndPosition(_normal, hitInfo.point + _normal * _shadowOffset); float distanceToPlane; SS_Ray ray = new SS_Ray(); // Calculate the shadow vertices if (_isPerspectiveProjection && useLightSource) { ray.direction = lightSourcePosition - _baseVertices[0]; ray.origin = _baseVertices[0]; shadowPlane.Raycast(ref ray, out distanceToPlane); _shadowVertices[0] = ray.origin + ray.direction * distanceToPlane; ray.direction = lightSourcePosition - _baseVertices[1]; ray.origin = _baseVertices[1]; shadowPlane.Raycast(ref ray, out distanceToPlane); _shadowVertices[1] = ray.origin + ray.direction * distanceToPlane; ray.direction = lightSourcePosition - _baseVertices[2]; ray.origin = _baseVertices[2]; shadowPlane.Raycast(ref ray, out distanceToPlane); _shadowVertices[2] = ray.origin + ray.direction * distanceToPlane; ray.direction = lightSourcePosition - _baseVertices[3]; ray.origin = _baseVertices[3]; shadowPlane.Raycast(ref ray, out distanceToPlane); _shadowVertices[3] = ray.origin + ray.direction * distanceToPlane; } else { ray.direction = actualLightVector; ray.origin = _baseVertices[0]; shadowPlane.Raycast(ref ray, out distanceToPlane); _shadowVertices[0] = ray.origin + ray.direction * distanceToPlane; ray.origin = _baseVertices[1]; shadowPlane.Raycast(ref ray, out distanceToPlane); _shadowVertices[1] = ray.origin + ray.direction * distanceToPlane; ray.origin = _baseVertices[2]; shadowPlane.Raycast(ref ray, out distanceToPlane); _shadowVertices[2] = ray.origin + ray.direction * distanceToPlane; ray.origin = _baseVertices[3]; shadowPlane.Raycast(ref ray, out distanceToPlane); _shadowVertices[3] = ray.origin + ray.direction * distanceToPlane; } // Calculate the shadow alpha float shadowAlpha = _initialAlpha; // Alpha base on distance to the surface float distance = hitInfo.distance; if (distance > _fadeDistance) { shadowAlpha = shadowAlpha - (distance - _fadeDistance) / (_projectionDistance - _fadeDistance) * shadowAlpha; } // Alpha based on shadow fall angle if (angleToNormal > _angleFadeMin) { shadowAlpha = shadowAlpha - (angleToNormal - _angleFadeMin) / (_angleFadeMax - _angleFadeMin) * shadowAlpha; } // Convert float alpha to byte _color.a = shadowAlpha; _color32.a = (byte)(shadowAlpha * 255f); _isVisible = true; } return(RecalculateShadowResult.Recalculated); }