Browse Source

Basic SSR hooked up to render compositor

BearishSun 8 years ago
parent
commit
7bb95a23b6

+ 19 - 10
Data/Raw/Engine/Includes/ImageBasedLighting.bslinc

@@ -29,6 +29,7 @@ mixin ImageBasedLighting
 		SamplerState gReflProbeSamp;
 		
 		Texture2D gAmbientOcclusionTex;
+		Texture2D gSSRTex;
 		
 		Texture2D gPreintegratedEnvBRDF;
 		SamplerState gPreintegratedEnvBRDFSamp;
@@ -134,7 +135,7 @@ mixin ImageBasedLighting
 			return lookupDir;
 		}
 		
-		float3 gatherReflectionRadiance(float3 worldPos, float3 dir, float roughness, float3 specularColor, uint probeOffset, uint numProbes)
+		float3 gatherReflectionRadiance(float3 worldPos, float3 dir, float roughness, float alpha, float3 specularColor, uint probeOffset, uint numProbes)
 		{
 			if(gUseReflectionMaps == 0)
 				return specularColor;
@@ -142,10 +143,9 @@ mixin ImageBasedLighting
 			float mipLevel = mapRoughnessToMipLevel(roughness, gReflCubemapNumMips);
 			
 			float3 output = 0;
-			float leftoverContribution = 1.0f;
 			for(uint i = 0; i < numProbes; i++)
 			{
-				if(leftoverContribution < 0.001f)
+				if(alpha < 0.001f)
 					break;
 						
 				uint probeIdx = gReflectionProbeIndices[probeOffset + i];
@@ -172,8 +172,8 @@ mixin ImageBasedLighting
 					float4 probeSample = gReflProbeCubemaps.SampleLevel(gReflProbeSamp, float4(correctedDir, probeData.cubemapIdx), mipLevel);
 					probeSample *= contribution;
 					
-					output += probeSample.rgb * leftoverContribution; 
-					leftoverContribution *= (1.0f - contribution);
+					output += probeSample.rgb * alpha; 
+					alpha *= (1.0f - contribution);
 				}
 			}
 				
@@ -182,7 +182,7 @@ mixin ImageBasedLighting
 				float skyMipLevel = mapRoughnessToMipLevel(roughness, gSkyCubemapNumMips);
 				float4 skySample = gSkyReflectionTex.SampleLevel(gSkyReflectionSamp, dir, skyMipLevel) * gSkyBrightness;
 				
-				output += skySample.rgb * leftoverContribution; 
+				output += skySample.rgb * alpha; 
 			}
 					
 			return output;
@@ -194,7 +194,7 @@ mixin ImageBasedLighting
 			return saturate(pow(NoV + ao, r2) - 1.0f + ao);
 		}		
 		
-		float3 getImageBasedSpecular(float3 worldPos, float3 V, float3 R, SurfaceData surfaceData, float ao, uint probeOffset, uint numProbes)
+		float3 getImageBasedSpecular(float3 worldPos, float3 V, float3 R, SurfaceData surfaceData, float ao, float4 ssr, uint probeOffset, uint numProbes)
 		{
 			// See C++ code for generation of gPreintegratedEnvBRDF to see why this code works as is
 			float3 N = surfaceData.worldNormal.xyz;
@@ -203,12 +203,21 @@ mixin ImageBasedLighting
 			// Note: Using a fixed F0 value of 0.04 (plastic) for dielectrics, and using albedo as specular for conductors.
 			// For more customizability allow the user to provide separate albedo/specular colors for both types.
 			float3 specularColor = lerp(float3(0.04f, 0.04f, 0.04f), surfaceData.albedo.rgb, surfaceData.metalness);
-			float3 radiance = gatherReflectionRadiance(worldPos, R, surfaceData.roughness, specularColor, probeOffset, numProbes);
 			
-			float2 envBRDF = gPreintegratedEnvBRDF.SampleLevel(gPreintegratedEnvBRDFSamp, float2(NoV, surfaceData.roughness), 0).rg;
+			// Get SSR
+			float3 radiance = ssr.rgb;
+			float alpha = 1.0f - ssr.a; // Determines how much to blend in reflection probes & skybox
+			
+			// Generate an approximate spec. occlusion value from AO. This doesn't need to be applied to SSR since it accounts
+			// for occlusion by tracing rays.
 			float specOcclusion = getSpecularOcclusion(NoV, surfaceData.roughness * surfaceData.roughness, ao);
+			alpha *= specOcclusion;
 			
-			return radiance * (specularColor * envBRDF.x + envBRDF.y) * specOcclusion;
+			// Get radiance from probes and skybox
+			radiance += gatherReflectionRadiance(worldPos, R, surfaceData.roughness, alpha, specularColor, probeOffset, numProbes);
+			
+			float2 envBRDF = gPreintegratedEnvBRDF.SampleLevel(gPreintegratedEnvBRDFSamp, float2(NoV, surfaceData.roughness), 0).rg;
+			return radiance * (specularColor * envBRDF.x + envBRDF.y);
 		}		
 	};
 };

+ 1 - 1
Data/Raw/Engine/Includes/LightingCommon.bslinc

@@ -175,7 +175,7 @@ mixin LightingCommon
 			float NoV = saturate(dot(N, V));
 			float NoL = saturate(dot(N, L));
 			
-			float3 diffuseColor = lerp(surfaceData.albedo.rgb, float3(0.0f, 0.0f, 0.0f), 1.0f - surfaceData.metalness);
+			float3 diffuseColor = lerp(surfaceData.albedo.rgb, float3(0.0f, 0.0f, 0.0f), surfaceData.metalness);
 			
 			// Note: Using a fixed F0 value of 0.04 (plastic) for dielectrics, and using albedo as specular for conductors.
 			// For more customizability allow the user to provide separate albedo/specular colors for both types.

+ 11 - 8
Data/Raw/Engine/Shaders/PPSSRTrace.bsl

@@ -66,8 +66,8 @@ technique PPSSRTrace
 			float roughness = surfData.roughness;
 			
 			
-			roughness = 0.3f;//DEBUG ONLY
-			
+			//roughness = 0.3f;//DEBUG ONLY
+			roughness = 0.0f;
 			
 			
 			float roughness2 = roughness * roughness;
@@ -91,7 +91,7 @@ technique PPSSRTrace
 			rayMarchParams.rayOrigin = P;
 			rayMarchParams.jitterOffset = jitterOffset;			
 			
-			int NUM_RAYS = 64; // DEBUG ONLY
+			int NUM_RAYS = 1; // DEBUG ONLY
 			
 			float4 sum = 0;
 			[loop]
@@ -113,14 +113,17 @@ technique PPSSRTrace
 				float3 tangentY = cross(N, tangentX);
 				
 				H = tangentX * H.x + tangentY * H.y + N * H.z; 
-				float3 R = 2 * dot( V, H ) * H - V;
-
+				//float3 R = 2 * dot( V, H ) * H - V;
+				float3 R = 2 * dot( V, N ) * N - V;
+				
 				// Eliminate rays pointing towards the viewer. They won't hit anything, plus they can screw up precision
 				// and cause ray step offset to be too small, causing self-intersections.
 				R = normalize(R); // Note: Normalization required?
-				if(dot(R, gViewDir) < 0.0f)
+				float dirFade = saturate(dot(R, gViewDir) * 10);
+				
+				if(dirFade < 0.00001f)
 					continue;
-					
+				
 				// Eliminate rays pointing below the surface
 				if(dot(R, N) < 0.0005f)
 					continue;
@@ -144,7 +147,7 @@ technique PPSSRTrace
 					// Tonemap the color to get a nicer visual average
 					color.rgb /= (1 + LuminanceRGB(color.rgb));
 					
-					sum += color;
+					sum += color * dirFade;
 				}
 			}
 			

+ 2 - 1
Data/Raw/Engine/Shaders/TiledDeferredImageBasedLighting.bsl

@@ -165,7 +165,8 @@ technique TiledDeferredImageBasedLighting
 			#endif				
 			
 			float ao = gAmbientOcclusionTex.Load(int3(pixelPos.xy, 0));
-			float3 imageBasedSpecular = getImageBasedSpecular(worldPosition, V, specR, surfaceData, ao, probeOffset, numProbes);
+			float4 ssr = gSSRTex.Load(int3(pixelPos.xy, 0));
+			float3 imageBasedSpecular = getImageBasedSpecular(worldPosition, V, specR, surfaceData, ao, ssr, probeOffset, numProbes);
 
 			float4 totalLighting = existingColor;
 			totalLighting.rgb += imageBasedSpecular;

+ 2 - 1
Data/Raw/Engine/Shaders/Transparent.bsl

@@ -83,7 +83,8 @@ mixin Surface
 			
 			float4 directLighting = getDirectLighting(input.worldPosition, V, specR, surfaceData, lightOffsets);
 			float ao = gAmbientOcclusionTex.Sample(gAlbedoSamp, input.uv0);
-			float3 imageBasedSpecular = getImageBasedSpecular(input.worldPosition, V, specR, surfaceData, ao, 
+			float4 ssr = gSSRTex.Load(int3(pixelPos.xy, 0));
+			float3 imageBasedSpecular = getImageBasedSpecular(input.worldPosition, V, specR, surfaceData, ao, ssr,
 				reflProbeOffsetAndSize.x, reflProbeOffsetAndSize.y);
 
 			float3 totalLighting = directLighting.rgb;

+ 1 - 1
Source/BansheeCore/Include/BsCAudioSource.h

@@ -15,7 +15,7 @@ namespace bs
 	/**
 	 * @copydoc	AudioSource
 	 *
-	 * Wraps AudioSource as a Component.
+	 * @note Wraps AudioSource as a Component.
 	 */
     class BS_CORE_EXPORT BS_SCRIPT_EXPORT(m:Audio,n:AudioSource) CAudioSource : public Component
     {

+ 2 - 2
Source/BansheeCore/Source/BsRenderSettings.cpp

@@ -65,7 +65,7 @@ namespace bs
 	}
 
 	AmbientOcclusionSettings::AmbientOcclusionSettings()
-		: enabled(true), radius(1.5f), bias(1.0f), fadeDistance(500.0f), fadeRange(50.0f), intensity(1.0f), power(1.0f)
+		: enabled(true), radius(1.5f), bias(1.0f), fadeDistance(500.0f), fadeRange(50.0f), intensity(1.0f), power(4.0f)
 		, quality(3)
 	{ }
 
@@ -109,7 +109,7 @@ namespace bs
 	}
 
 	RenderSettings::RenderSettings()
-		: enableAutoExposure(true), enableTonemapping(true), enableFXAA(false), exposureScale(0.0f), gamma(2.2f)
+		: enableAutoExposure(true), enableTonemapping(true), enableFXAA(true), exposureScale(0.0f), gamma(2.2f)
 		, enableHDR(true), enableLighting(true), enableShadows(true), enableIndirectLighting(true), overlayOnly(false)
 	{ }
 

+ 3 - 0
Source/RenderBeast/Include/BsImageBasedLighting.h

@@ -62,6 +62,7 @@ namespace bs { namespace ct
 		BS_PARAM_BLOCK_ENTRY(INT32, gSkyCubemapAvailable)
 		BS_PARAM_BLOCK_ENTRY(INT32, gUseReflectionMaps)
 		BS_PARAM_BLOCK_ENTRY(INT32, gSkyCubemapNumMips)
+		BS_PARAM_BLOCK_ENTRY(float, gSkyBrightness)
 	BS_PARAM_BLOCK_END
 
 	extern ReflProbeParamsParamDef gReflProbeParamsParamDef;
@@ -102,6 +103,7 @@ namespace bs { namespace ct
 
 		GpuParamTexture skyReflectionsTexParam;
 		GpuParamTexture ambientOcclusionTexParam;
+		GpuParamTexture ssrTexParam;
 		GpuParamTexture reflectionProbeCubemapsTexParam;
 
 		GpuParamTexture preintegratedEnvBRDFParam;
@@ -138,6 +140,7 @@ namespace bs { namespace ct
 			SPtr<GpuBuffer> sceneColorBuffer;
 			SPtr<Texture> preIntegratedGF;
 			SPtr<Texture> ambientOcclusion;
+			SPtr<Texture> ssr;
 		};
 
 		TiledDeferredImageBasedLightingMat();

+ 16 - 0
Source/RenderBeast/Include/BsRenderCompositor.h

@@ -579,5 +579,21 @@ namespace ct
 		void clear() override;
 	};
 
+	/** Renders screen space reflections. */
+	class RCNodeSSR : public RenderCompositorNode
+	{
+	public:
+		SPtr<PooledRenderTexture> output;
+
+		static StringID getNodeId() { return "SSR"; }
+		static SmallVector<StringID, 4> getDependencies(const RendererView& view);
+	protected:
+		/** @copydoc RenderCompositorNode::render */
+		void render(const RenderCompositorNodeInputs& inputs) override;
+
+		/** @copydoc RenderCompositorNode::clear */
+		void clear() override;
+	};
+
 	/** @} */
 }}

+ 5 - 4
Source/RenderBeast/Source/BsImageBasedLighting.cpp

@@ -123,6 +123,9 @@ namespace bs { namespace ct
 		// AO
 		params->getTextureParam(programType, "gAmbientOcclusionTex", ambientOcclusionTexParam);
 
+		// SSR
+		params->getTextureParam(programType, "gSSRTex", ssrTexParam);
+
 		if(gridIndices)
 		{
 			if (!optional || params->hasBuffer(programType, "gReflectionProbeIndices"))
@@ -166,6 +169,7 @@ namespace bs { namespace ct
 
 		gReflProbeParamsParamDef.gReflCubemapNumMips.set(buffer, numReflProbeMips);
 		gReflProbeParamsParamDef.gUseReflectionMaps.set(buffer, capturingReflections ? 0 : 1);
+		gReflProbeParamsParamDef.gSkyBrightness.set(buffer, brightness);
 	}
 
 	ShaderVariation TiledDeferredImageBasedLightingMat::VAR_1MSAA = ShaderVariation({
@@ -250,18 +254,15 @@ namespace bs { namespace ct
 		mGBufferDepth.set(inputs.gbuffer.depth);
 
 		SPtr<Texture> skyFilteredRadiance;
-		SPtr<Texture> skyIrradiance;
 		if(sceneInfo.skybox)
-		{
 			skyFilteredRadiance = sceneInfo.skybox->getFilteredRadiance();
-			skyIrradiance = sceneInfo.skybox->getIrradiance();
-		}
 
 		mImageBasedParams.preintegratedEnvBRDFParam.set(inputs.preIntegratedGF);
 		mImageBasedParams.reflectionProbesParam.set(probeData.getProbeBuffer());
 		mImageBasedParams.reflectionProbeCubemapsTexParam.set(sceneInfo.reflProbeCubemapsTex);
 		mImageBasedParams.skyReflectionsTexParam.set(skyFilteredRadiance);
 		mImageBasedParams.ambientOcclusionTexParam.set(inputs.ambientOcclusion);
+		mImageBasedParams.ssrTexParam.set(inputs.ssr);
 
 		mParamsSet->setParamBlockBuffer("PerCamera", view.getPerViewBuffer(), true);
 

+ 5 - 2
Source/RenderBeast/Source/BsRenderBeast.cpp

@@ -96,6 +96,7 @@ namespace bs { namespace ct
 		RenderCompositor::registerNodeType<RCNodeSSAO>();
 		RenderCompositor::registerNodeType<RCNodeClusteredForward>();
 		RenderCompositor::registerNodeType<RCNodeIndirectLighting>();
+		RenderCompositor::registerNodeType<RCNodeSSR>();
 	}
 
 	void RenderBeast::destroyCore()
@@ -575,6 +576,8 @@ namespace bs { namespace ct
 		settings->enableHDR = hdr;
 		settings->enableShadows = false; // Note: If I ever change this I need to make sure that shadow map rendering is aware of this view (currently it is only aware of main camera views)
 		settings->enableIndirectLighting = false;
+		settings->screenSpaceReflections.enabled = false;
+		settings->ambientOcclusion.enabled = false;
 
 		Matrix4 viewOffsetMat = Matrix4::translation(-position);
 
@@ -600,7 +603,7 @@ namespace bs { namespace ct
 				up = -Vector3::UNIT_Z;
 				break;
 			case CF_NegativeY:
-				forward = Vector3::UNIT_X; // TODO: Why X here?
+				forward = -Vector3::UNIT_Y;
 				up = Vector3::UNIT_Z;
 				break;
 			case CF_PositiveZ:
@@ -612,7 +615,7 @@ namespace bs { namespace ct
 			}
 
 			Vector3 right = Vector3::cross(up, forward);
-			viewRotationMat = Matrix3(right, up, forward); // TODO - Use -forward here? (Works for shadows)
+			viewRotationMat = Matrix3(right, up, forward);
 
 			viewDesc.viewDirection = forward;
 			viewDesc.viewTransform = Matrix4(viewRotationMat) * viewOffsetMat;

+ 81 - 6
Source/RenderBeast/Source/BsRenderCompositor.cpp

@@ -749,13 +749,15 @@ namespace bs { namespace ct
 
 	void RCNodeTiledDeferredIBL::render(const RenderCompositorNodeInputs& inputs)
 	{
+		const RenderSettings& rs = inputs.view.getRenderSettings();
+
 		RCNodeSceneColor* sceneColorNode = static_cast<RCNodeSceneColor*>(inputs.inputNodes[0]);
 		RCNodeGBuffer* gbufferNode = static_cast<RCNodeGBuffer*>(inputs.inputNodes[1]);
 		RCNodeSceneDepth* sceneDepthNode = static_cast<RCNodeSceneDepth*>(inputs.inputNodes[2]);
 		RCNodeLightAccumulation* lightAccumNode = static_cast <RCNodeLightAccumulation*>(inputs.inputNodes[3]);
 
 		SPtr<Texture> ssao;
-		if (inputs.view.getRenderSettings().ambientOcclusion.enabled)
+		if (rs.ambientOcclusion.enabled)
 		{
 			RCNodeSSAO* ssaoNode = static_cast<RCNodeSSAO*>(inputs.inputNodes[5]);
 			ssao = ssaoNode->output->texture;
@@ -763,6 +765,17 @@ namespace bs { namespace ct
 		else
 			ssao = Texture::WHITE;
 
+		SPtr<Texture> ssr;
+		if (rs.screenSpaceReflections.enabled)
+		{
+			UINT32 nodeIdx = rs.ambientOcclusion.enabled ? 6 : 5;
+
+			RCNodeSSR* ssrNode = static_cast<RCNodeSSR*>(inputs.inputNodes[nodeIdx]);
+			ssr = ssrNode->output->texture;
+		}
+		else
+			ssr = Texture::BLACK;
+
 		const RendererViewProperties& viewProps = inputs.view.getProperties();
 		TiledDeferredImageBasedLightingMat* material = TiledDeferredImageBasedLightingMat::getVariation(viewProps.numSamples);
 
@@ -775,6 +788,7 @@ namespace bs { namespace ct
 		iblInputs.lightAccumulation = lightAccumNode->lightAccumulationTex->texture;
 		iblInputs.preIntegratedGF = RendererTextures::preintegratedEnvGF;
 		iblInputs.ambientOcclusion = ssao;
+		iblInputs.ssr = ssr;
 
 		if(sceneColorNode->flattenedSceneColorBuffer)
 			iblInputs.sceneColorBuffer = sceneColorNode->flattenedSceneColorBuffer->buffer;
@@ -800,6 +814,9 @@ namespace bs { namespace ct
 		if(view.getRenderSettings().ambientOcclusion.enabled)
 			deps.push_back(RCNodeSSAO::getNodeId());
 
+		if (view.getRenderSettings().screenSpaceReflections.enabled)
+			deps.push_back(RCNodeSSR::getNodeId());
+
 		return deps;
 	}
 
@@ -824,6 +841,11 @@ namespace bs { namespace ct
 		output = nullptr;
 	}
 
+	SmallVector<StringID, 4> RCNodeUnflattenSceneColor::getDependencies(const RendererView& view)
+	{
+		return { RCNodeSceneColor::getNodeId() };
+	}
+
 	void RCNodeClusteredForward::render(const RenderCompositorNodeInputs& inputs)
 	{
 		const SceneInfo& sceneInfo = inputs.scene;
@@ -885,6 +907,7 @@ namespace bs { namespace ct
 
 				iblParams.skyReflectionsTexParam.set(skyFilteredRadiance);
 				iblParams.ambientOcclusionTexParam.set(Texture::WHITE); // Note: Add SSAO here?
+				iblParams.ssrTexParam.set(Texture::BLACK); // Note: Add SSR here?
 
 				iblParams.reflectionProbeCubemapsTexParam.set(sceneInfo.reflProbeCubemapsTex);
 				iblParams.preintegratedEnvBRDFParam.set(RendererTextures::preintegratedEnvGF);
@@ -934,11 +957,6 @@ namespace bs { namespace ct
 		return { RCNodeSceneColor::getNodeId(), RCNodeSkybox::getNodeId() };
 	}
 
-	SmallVector<StringID, 4> RCNodeUnflattenSceneColor::getDependencies(const RendererView& view)
-	{
-		return { RCNodeSceneColor::getNodeId() };
-	}
-
 	void RCNodeSkybox::render(const RenderCompositorNodeInputs& inputs)
 	{
 		Skybox* skybox = inputs.scene.skybox;
@@ -1692,4 +1710,61 @@ namespace bs { namespace ct
 	{
 		return { RCNodeResolvedSceneDepth::getNodeId(), RCNodeGBuffer::getNodeId() };
 	}
+
+	void RCNodeSSR::render(const RenderCompositorNodeInputs& inputs)
+	{
+		RCNodeSceneDepth* sceneDepthNode = static_cast<RCNodeSceneDepth*>(inputs.inputNodes[0]);
+		RCNodeLightAccumulation* lightAccumNode = static_cast<RCNodeLightAccumulation*>(inputs.inputNodes[1]);
+		RCNodeGBuffer* gbufferNode = static_cast<RCNodeGBuffer*>(inputs.inputNodes[2]);
+		RCNodeHiZ* hiZNode = static_cast<RCNodeHiZ*>(inputs.inputNodes[3]);
+
+		GpuResourcePool& resPool = GpuResourcePool::instance();
+		const RendererViewProperties& viewProps = inputs.view.getProperties();
+		const ScreenSpaceReflectionsSettings& settings = inputs.view.getRenderSettings().screenSpaceReflections;
+
+		SPtr<Texture> hiZ = hiZNode->output->texture;
+
+		// This will be executing before scene color is resolved
+		SPtr<Texture> sceneColor = lightAccumNode->lightAccumulationTex->texture;
+
+		GBufferTextures gbuffer;
+		gbuffer.albedo = gbufferNode->albedoTex->texture;
+		gbuffer.normals = gbufferNode->normalTex->texture;
+		gbuffer.roughMetal = gbufferNode->roughMetalTex->texture;
+		gbuffer.depth = sceneDepthNode->depthTex->texture;
+
+		UINT32 width = viewProps.viewRect.width;
+		UINT32 height = viewProps.viewRect.height;
+
+		output = resPool.get(POOLED_RENDER_TEXTURE_DESC::create2D(PF_RGBA16F, width, height, TU_RENDERTARGET));
+
+		// TODO - Run SSRStencil
+
+		SSRTraceMat* traceMat = SSRTraceMat::get();
+		traceMat->execute(inputs.view, gbuffer, sceneColor, hiZ, settings, output->renderTexture);
+
+		// TODO - Run temporal resolve
+
+		RenderAPI::instance().setRenderTarget(nullptr);
+	}
+
+	void RCNodeSSR::clear()
+	{
+		GpuResourcePool& resPool = GpuResourcePool::instance();
+		resPool.release(output);
+	}
+
+	SmallVector<StringID, 4> RCNodeSSR::getDependencies(const RendererView& view)
+	{
+		SmallVector<StringID, 4> deps;
+		deps.push_back(RCNodeSceneDepth::getNodeId());
+		deps.push_back(RCNodeLightAccumulation::getNodeId());
+		deps.push_back(RCNodeGBuffer::getNodeId());
+		deps.push_back(RCNodeHiZ::getNodeId());
+
+		if (view.getProperties().numSamples > 1)
+			deps.push_back(RCNodeUnflattenLightAccum::getNodeId());
+
+		return deps;
+	}
 }}

+ 1 - 0
Source/RenderBeast/Source/BsRendererView.cpp

@@ -138,6 +138,7 @@ namespace bs { namespace ct
 	{
 		mCamera = desc.sceneCamera;
 		mProperties = desc;
+		mProperties.viewProjTransform = desc.projTransform * desc.viewTransform;
 		mTargetDesc = desc.target;
 
 		setStateReductionMode(desc.stateReduction);

+ 4 - 0
Source/SBansheeEngine/Include/BsScriptComponent.h

@@ -23,6 +23,10 @@ namespace bs
 		/** Returns the component wrapped by this object. */
 		HComponent getComponent() const { return static_object_cast<Component>(getNativeHandle()); }
 
+		/** Returns the component wrapped by this object. */
+		template<class T>
+		GameObjectHandle<T> getComponent() const { return static_object_cast<T>(getNativeHandle()); }
+
 	protected:
 		friend class ScriptGameObjectManager;