diff --git a/samples/gltf-camera/.gitignore b/samples/gltf-camera/.gitignore
new file mode 100644
index 00000000..42afabfd
--- /dev/null
+++ b/samples/gltf-camera/.gitignore
@@ -0,0 +1 @@
+/build
\ No newline at end of file
diff --git a/samples/gltf-camera/build.gradle b/samples/gltf-camera/build.gradle
new file mode 100644
index 00000000..529f7361
--- /dev/null
+++ b/samples/gltf-camera/build.gradle
@@ -0,0 +1,55 @@
+plugins {
+ id 'com.android.application'
+ id 'kotlin-android'
+}
+
+android {
+ namespace "io.github.sceneview.sample.gltfcamera"
+
+ compileSdk 34
+
+ defaultConfig {
+ applicationId "io.github.sceneview.sample.gltfcamera"
+ minSdk 28
+ targetSdk 34
+ versionCode 1
+ versionName "1.0.0"
+ }
+
+ buildTypes {
+ release {
+ }
+ }
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+ kotlinOptions {
+ jvmTarget = JavaVersion.VERSION_17
+ }
+ buildFeatures {
+ compose true
+ }
+ composeOptions {
+ kotlinCompilerExtensionVersion = "1.5.14"
+ }
+ androidResources {
+ noCompress 'filamat', 'ktx'
+ }
+}
+
+dependencies {
+ implementation project(":samples:common")
+
+ implementation "androidx.compose.ui:ui:1.6.7"
+ implementation "androidx.compose.foundation:foundation:1.6.7"
+ implementation 'androidx.activity:activity-compose:1.9.0'
+ implementation 'androidx.compose.material:material:1.6.7'
+ implementation "androidx.compose.ui:ui-tooling-preview:1.6.7"
+ implementation "androidx.navigation:navigation-compose:2.7.7"
+ debugImplementation "androidx.compose.ui:ui-tooling:1.6.7"
+
+ // SceneView
+ releaseImplementation "io.github.sceneview:sceneview:2.2.1"
+ debugImplementation project(":sceneview")
+}
diff --git a/samples/gltf-camera/src/main/AndroidManifest.xml b/samples/gltf-camera/src/main/AndroidManifest.xml
new file mode 100644
index 00000000..93ea670d
--- /dev/null
+++ b/samples/gltf-camera/src/main/AndroidManifest.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/samples/gltf-camera/src/main/assets/environments/symmetrical_garden_02_4k.hdr b/samples/gltf-camera/src/main/assets/environments/symmetrical_garden_02_4k.hdr
new file mode 100644
index 00000000..ea40cea0
Binary files /dev/null and b/samples/gltf-camera/src/main/assets/environments/symmetrical_garden_02_4k.hdr differ
diff --git a/samples/gltf-camera/src/main/assets/models/halls_green_haa_emplacement.glb b/samples/gltf-camera/src/main/assets/models/halls_green_haa_emplacement.glb
new file mode 100644
index 00000000..091feae3
Binary files /dev/null and b/samples/gltf-camera/src/main/assets/models/halls_green_haa_emplacement.glb differ
diff --git a/samples/gltf-camera/src/main/java/io/github/sceneview/sample/gltfcamera/MainActivity.kt b/samples/gltf-camera/src/main/java/io/github/sceneview/sample/gltfcamera/MainActivity.kt
new file mode 100644
index 00000000..28eaffa7
--- /dev/null
+++ b/samples/gltf-camera/src/main/java/io/github/sceneview/sample/gltfcamera/MainActivity.kt
@@ -0,0 +1,174 @@
+package io.github.sceneview.sample.gltfcamera
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxScope
+import androidx.compose.foundation.layout.ExperimentalLayoutApi
+import androidx.compose.foundation.layout.FlowRow
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.navigationBarsPadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Done
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.FilterChip
+import androidx.compose.material3.FilterChipDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
+import io.github.sceneview.Scene
+import io.github.sceneview.node.ModelNode
+import io.github.sceneview.rememberEngine
+import io.github.sceneview.rememberEnvironmentLoader
+import io.github.sceneview.rememberModelLoader
+import io.github.sceneview.rememberNode
+import io.github.sceneview.sample.SceneviewTheme
+
+private const val kAperture = 16f
+private const val kShutterSpeed = 1f / 125f
+private const val kSensitivity = 100f
+
+class MainActivity : ComponentActivity() {
+
+ @OptIn(
+ ExperimentalMaterial3Api::class
+ )
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ setContent {
+ SceneviewTheme {
+ Box(modifier = Modifier.fillMaxSize()) {
+ val engine = rememberEngine()
+ val modelLoader = rememberModelLoader(engine)
+ val environmentLoader = rememberEnvironmentLoader(engine)
+
+ val modelNode = rememberNode {
+ ModelNode(
+ modelInstance = modelLoader.createModelInstance(
+ assetFileLocation = "models/halls_green_haa_emplacement.glb"
+ )
+ )
+ }
+ val cameraNodes = remember(modelNode) {
+ modelNode.cameraNodes.onEach {
+ it.setExposure(kAperture, kShutterSpeed, kSensitivity)
+ }
+ }
+
+ var selectedCameraNode by remember { mutableStateOf(cameraNodes[0]) }
+
+ Scene(
+ modifier = Modifier
+ .fillMaxSize()
+ .onSizeChanged { modelNode.updateCamerasProjection(it) },
+ engine = engine,
+ modelLoader = modelLoader,
+ cameraNode = selectedCameraNode,
+ childNodes = listOf(modelNode),
+ environment = environmentLoader.createHDREnvironment(
+ assetFileLocation = "environments/symmetrical_garden_02_4k.hdr"
+ )!!
+ )
+ ChipsGroup(
+ labels = cameraNodes.map { it.name ?: "" },
+ onSelected = {
+ selectedCameraNode = cameraNodes[it]
+ }
+ )
+
+ TopAppBar(
+ title = {
+ Image(
+ modifier = Modifier
+ .width(192.dp),
+ painter = painterResource(id = R.drawable.logo),
+ contentDescription = "Logo"
+ )
+ },
+ colors = TopAppBarDefaults.topAppBarColors(
+ containerColor = MaterialTheme.colorScheme.primary.copy(alpha = 0.25f),
+ titleContentColor = MaterialTheme.colorScheme.onPrimary
+
+ )
+ )
+ }
+ }
+ }
+ }
+
+ @OptIn(ExperimentalLayoutApi::class)
+ @Composable
+ fun BoxScope.ChipsGroup(labels: List, onSelected: (index: Int) -> Unit) {
+ var selectedIndex by remember { mutableIntStateOf(0) }
+ FlowRow(
+ modifier = Modifier
+ .fillMaxWidth()
+ .align(Alignment.BottomEnd)
+ .background(MaterialTheme.colorScheme.primaryContainer.copy(alpha = 0.5f))
+ .navigationBarsPadding()
+ .padding(8.dp)
+ ) {
+
+ labels.forEachIndexed { index, label ->
+ val selected = selectedIndex == index
+ FilterChip(
+ label = {
+ Text(
+ style = MaterialTheme.typography.bodyLarge.copy(),
+ text = label
+ )
+ },
+ modifier = Modifier.padding(4.dp),
+ selected = selected,
+ onClick = {
+ selectedIndex = index
+ onSelected(index)
+ },
+ leadingIcon = if (selected) {
+ {
+ Icon(
+ imageVector = Icons.Filled.Done,
+ contentDescription = "Done icon",
+ modifier = Modifier.size(FilterChipDefaults.IconSize)
+ )
+ }
+ } else {
+ null
+ }
+ )
+ }
+ }
+ }
+}
+
+fun ModelNode.updateCamerasProjection(viewPortSize: IntSize) {
+ cameraNodes.forEach { cameraNode ->
+ cameraNode.updateProjection(
+ aspect = viewPortSize.let { it.width.toDouble() / it.height.toDouble() },
+ near = 0.05f,
+ far = 5000.0f
+ )
+ }
+}
\ No newline at end of file
diff --git a/samples/gltf-camera/src/main/res/values/strings.xml b/samples/gltf-camera/src/main/res/values/strings.xml
new file mode 100644
index 00000000..f5eadbe6
--- /dev/null
+++ b/samples/gltf-camera/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ glTF Camera
+
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index 6c1aa257..5f52e5d3 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -17,5 +17,6 @@ include ':samples:ar-cloud-anchor'
include ':samples:ar-model-viewer'
include ':samples:ar-model-viewer-compose'
include ':samples:ar-point-cloud'
+include ':samples:gltf-camera'
include ':samples:model-viewer'
include ':samples:model-viewer-compose'
\ No newline at end of file