Skip to content

Commit

Permalink
Add material-builder Android sample (#884)
Browse files Browse the repository at this point in the history
  • Loading branch information
bejado authored Feb 25, 2019
1 parent 186920e commit 2378e64
Show file tree
Hide file tree
Showing 36 changed files with 1,513 additions and 0 deletions.
10 changes: 10 additions & 0 deletions android/samples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,16 @@ Demonstrates how to render into a `TextureView` instead of a `SurfaceView`:

![Texture View](../../docs/images/samples/sample_texture_view.jpg)

### `material-builder`

Demonstrates how to programatically generate Filament materials, as opposed to compiling them on the
host machine:

![Material Builder](../../docs/images/samples/sample_image_based_lighting.jpg)

The `material-builder` sample requires the `filamat` static library which can be compiled by adding
the `-l` flag to the `./build.sh` command.

## Prerequisites

Before you start, make sure to read [Filament's README](../../README.md). You need to be able to
Expand Down
12 changes: 12 additions & 0 deletions android/samples/material-builder/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
/.idea/caches
/.idea/gradle.xml
.DS_Store
/build
/captures
/app/src/main/assets/envs
.externalNativeBuild
1 change: 1 addition & 0 deletions android/samples/material-builder/app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
92 changes: 92 additions & 0 deletions android/samples/material-builder/app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

apply from: '../../../build/filament-tasks.gradle'

compileMesh {
group 'Filament'
description 'Compile mesh'

inputFile = file("../../../../third_party/shader_ball/shader_ball.obj")
outputDir = file("src/main/assets/models")
}

generateIbl {
group 'Filament'
description 'Generate IBL'

inputFile = file("../../../../third_party/environments/flower_road_2k.hdr")
outputDir = file("src/main/assets/envs")
}

preBuild.dependsOn compileMesh
preBuild.dependsOn generateIbl

android {
compileSdkVersion 28
defaultConfig {
applicationId "com.google.android.filament.ibl"
minSdkVersion 21
targetSdkVersion 28
versionCode 1
versionName "1.0"
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}

// Filament comes with native code, the following declarations
// can be used to generate architecture specific APKs
flavorDimensions 'cpuArch'
productFlavors {
arm8 {
dimension 'cpuArch'
ndk {
abiFilters 'arm64-v8a'
}
}
arm7 {
dimension 'cpuArch'
ndk {
abiFilters 'armeabi-v7a'
}
}
x86_64 {
dimension 'cpuArch'
ndk {
abiFilters 'x86_64'
}
}
x86 {
dimension 'cpuArch'
ndk {
abiFilters 'x86'
}
}
universal {
dimension 'cpuArch'
}
}

// We use the .filamat extension for materials compiled with matc
// Telling aapt to not compress them allows to load them efficiently
aaptOptions {
noCompress 'filamat'
}
}

dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"

// Depend on Filament
implementation 'com.google.android.filament:filament-android'

// Depend on Filamat
implementation 'com.google.android.filament:filamat-android'
}
21 changes: 21 additions & 0 deletions android/samples/material-builder/app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
21 changes: 21 additions & 0 deletions android/samples/material-builder/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.filament.ibl">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>

<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>

</manifest>
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.filament.ibl

import android.content.res.AssetManager
import android.graphics.BitmapFactory

import com.google.android.filament.Engine
import com.google.android.filament.IndirectLight
import com.google.android.filament.Skybox
import com.google.android.filament.Texture
import java.io.BufferedReader
import java.io.InputStreamReader

import java.nio.ByteBuffer

import kotlin.math.log2

data class Ibl(val indirectLight: IndirectLight,
val indirectLightTexture: Texture,
val skybox: Skybox,
val skyboxTexture: Texture)

fun loadIbl(assets: AssetManager, name: String, engine: Engine): Ibl {
val (ibl, iblTexture) = loadIndirectLight(assets, name, engine)
val (skybox, skyboxTexture) = loadSkybox(assets, name, engine)
return Ibl(ibl, iblTexture, skybox, skyboxTexture)
}

fun destroyIbl(engine: Engine, ibl: Ibl) {
engine.destroySkybox(ibl.skybox)
engine.destroyTexture(ibl.skyboxTexture)
engine.destroyIndirectLight(ibl.indirectLight)
engine.destroyTexture(ibl.indirectLightTexture)
}

private fun peekSize(assets: AssetManager, name: String): Pair<Int, Int> {
assets.open(name).use { input ->
val opts = BitmapFactory.Options().apply { inJustDecodeBounds = true }
BitmapFactory.decodeStream(input, null, opts)
return opts.outWidth to opts.outHeight
}
}

private fun loadIndirectLight(
assets: AssetManager,
name: String,
engine: Engine): Pair<IndirectLight, Texture> {
val (w, h) = peekSize(assets, "$name/m0_nx.rgbm")
val texture = Texture.Builder()
.width(w)
.height(h)
.levels(log2(w.toFloat()).toInt() + 1)
.format(Texture.InternalFormat.RGBA8)
.rgbm(true)
.sampler(Texture.Sampler.SAMPLER_CUBEMAP)
.build(engine)

repeat(texture.levels) {
loadCubemap(texture, assets, name, engine, "m${it}_", it)
}

val sphericalHarmonics = loadSphericalHarmonics(assets, name)

return IndirectLight.Builder()
.reflections(texture)
.irradiance(3, sphericalHarmonics)
.intensity(30_000.0f)
.build(engine) to texture
}

private fun loadSphericalHarmonics(assets: AssetManager, name: String): FloatArray {
val re = Regex("""\(\s*([+-]?\d+\.\d+),\s*([+-]?\d+\.\d+),\s*([+-]?\d+\.\d+)\);""")
// 3 bands = 9 RGB coefficients, so 9 * 3 floats
val sphericalHarmonics = FloatArray(9 * 3)
BufferedReader(InputStreamReader(assets.open("$name/sh.txt"))).use { input ->
repeat(9) { i ->
val line = input.readLine()
re.find(line)?.let {
sphericalHarmonics[i * 3] = it.groups[1]?.value?.toFloat() ?: 0.0f
sphericalHarmonics[i * 3 + 1] = it.groups[2]?.value?.toFloat() ?: 0.0f
sphericalHarmonics[i * 3 + 2] = it.groups[3]?.value?.toFloat() ?: 0.0f
}
}
}
return sphericalHarmonics
}

private fun loadSkybox(assets: AssetManager, name: String, engine: Engine): Pair<Skybox, Texture> {
val (w, h) = peekSize(assets, "$name/nx.rgbm")
val texture = Texture.Builder()
.width(w)
.height(h)
.levels(1)
.format(Texture.InternalFormat.RGBA8)
.rgbm(true)
.sampler(Texture.Sampler.SAMPLER_CUBEMAP)
.build(engine)

loadCubemap(texture, assets, name, engine)

return Skybox.Builder().environment(texture).build(engine) to texture
}

private fun loadCubemap(texture: Texture,
assets: AssetManager,
name: String,
engine: Engine,
prefix: String = "",
level: Int = 0) {
// This is important, in the RGBM format the alpha channel does not encode
// opacity but a scale factor (to represent HDR data). We must tell Android
// to not premultiply the RGB channels by the alpha channel
val opts = BitmapFactory.Options().apply { inPremultiplied = false }

// RGBM is always 4 bytes per pixel
val faceSize = texture.getWidth(level) * texture.getHeight(level) * 4
val offsets = IntArray(6) { it * faceSize }
// Allocate enough memory for all the cubemap faces
val storage = ByteBuffer.allocateDirect(faceSize * 6)

arrayOf("px", "nx", "py", "ny", "pz", "nz").forEach { suffix ->
assets.open("$name/$prefix$suffix.rgbm").use {
val bitmap = BitmapFactory.decodeStream(it, null, opts)
bitmap?.copyPixelsToBuffer(storage)
}
}

// Rewind the texture buffer
storage.flip()
android.util.Log.d("Texture", "Cubemap level: $level $faceSize ${texture.getWidth(level)}")

val buffer = Texture.PixelBufferDescriptor(storage, Texture.Format.RGBM, Texture.Type.UBYTE)
texture.setImage(engine, level, buffer, offsets)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2018 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.android.filament.ibl

import java.io.InputStream
import java.nio.ByteBuffer
import java.nio.ByteOrder

fun readIntLE(input: InputStream): Int {
return input.read() and 0xff or (
input.read() and 0xff shl 8) or (
input.read() and 0xff shl 16) or (
input.read() and 0xff shl 24)
}

fun readFloat32LE(input: InputStream): Float {
val bytes = ByteArray(4)
input.read(bytes, 0, 4)
return ByteBuffer.wrap(bytes).order(ByteOrder.LITTLE_ENDIAN).float
}

fun readUIntLE(input: InputStream): Long {
return readIntLE(input).toLong() and 0xFFFFFFFFL
}
Loading

0 comments on commit 2378e64

Please sign in to comment.