/* * Copyright (C) 2019 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. */ #include "lucy_utils.h" #include #include #include #include #include #include #include #include #include #include #include using namespace filament; using namespace filament::math; using namespace utils; using filament::geometry::SurfaceOrientation; #define FILTER_SIZE 9 namespace LucyUtils { constexpr float M_PIf = float(M_PI); Entity createQuad(Engine* engine, Texture* tex, ImageOp op, Texture* secondary) { static VertexBuffer* vertexBuffer = [](Engine& engine) { struct OverlayVertex { float2 position; float2 uv; quath tangent; }; static OverlayVertex verts[4] = { {{0, 0}, {0, 0}, {0, 0, 0, 1}}, {{1, 0}, {1, 0}, {0, 0, 0, 1}}, {{0, 1}, {0, 1}, {0, 0, 0, 1}}, {{1, 1}, {1, 1}, {0, 0, 0, 1}} }; static float3 normals[4] = { {0, 0, 1}, {0, 0, 1}, {0, 0, 1}, {0, 0, 1}, }; SurfaceOrientation::Builder() .vertexCount(4) .normals(normals) .build() .getQuats(2 + (quath*) verts, 4, 24); auto vb = VertexBuffer::Builder() .vertexCount(4) .bufferCount(1) .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT2, 0, 24) .attribute(VertexAttribute::UV0, 0, VertexBuffer::AttributeType::FLOAT2, 8, 24) .attribute(VertexAttribute::TANGENTS, 0, VertexBuffer::AttributeType::HALF4, 16, 24) .build(engine); vb->setBufferAt(engine, 0, VertexBuffer::BufferDescriptor(verts, sizeof(verts), nullptr)); return vb; }(*engine); static IndexBuffer* indexBuffer = [](Engine& engine) { static constexpr uint16_t indices[6] = { 2, 1, 0, 1, 2, 3 }; auto ib = IndexBuffer::Builder() .indexCount(6) .bufferType(IndexBuffer::IndexType::USHORT) .build(engine); ib->setBuffer(engine, IndexBuffer::BufferDescriptor(indices, sizeof(indices), nullptr)); return ib; }(*engine); static Material* blit = [](Engine& engine) { filamat::Package pkg = filamat::MaterialBuilder() .name("blit") .material(R"SHADER( void material(inout MaterialInputs material) { prepareMaterial(material); material.baseColor = texture(materialParams_color, getUV0()); } )SHADER") .require(VertexAttribute::UV0) .parameter(filamat::MaterialBuilder::SamplerType::SAMPLER_2D, "color") .targetApi(filamat::MaterialBuilder::TargetApi::OPENGL) .shading(Shading::UNLIT) .depthWrite(false) .depthCulling(false) .build(); return Material::Builder().package(pkg.getData(), pkg.getSize()).build(engine); }(*engine); static Material* mix = [](Engine& engine) { filamat::Package pkg = filamat::MaterialBuilder() .name("mix") .material(R"SHADER( void material(inout MaterialInputs material) { prepareMaterial(material); vec4 primary = texture(materialParams_color, getUV0()); vec4 blurred = texture(materialParams_secondary, getUV0()); // HACK: this is a crude bloom effect float L = max(0.0, max(blurred.r, max(blurred.g, blurred.b)) - 1.0); material.baseColor = mix(primary, blurred, min(1.0, L)); } )SHADER") .require(VertexAttribute::UV0) .parameter(filamat::MaterialBuilder::SamplerType::SAMPLER_2D, "color") .parameter(filamat::MaterialBuilder::SamplerType::SAMPLER_2D, "secondary") .targetApi(filamat::MaterialBuilder::TargetApi::OPENGL) .shading(Shading::UNLIT) .depthWrite(false) .depthCulling(false) .build(); return Material::Builder().package(pkg.getData(), pkg.getSize()).build(engine); }(*engine); static const float4* weights = []() { static float4 weights[FILTER_SIZE * 2]; float4* hweights = weights; float4* vweights = weights + FILTER_SIZE; static const float radius = 2; const auto filter = [](float t) { t /= 2.0; if (t >= 1.0) return 0.0f; const float scale = 1.0f / std::sqrt(0.5f * M_PIf); return std::exp(-2.0f * t * t) * scale; }; constexpr float pixelWidth = 2.0f / float(FILTER_SIZE); float sum = 0.0f; for (int i = 0; i < FILTER_SIZE; i++) { float x = -1.0f + pixelWidth / 2.0f + pixelWidth * i; hweights[i].x = vweights[i].x = filter(std::abs(x)); hweights[i].y = vweights[i].z = radius * (i - (FILTER_SIZE - 1) / 2); hweights[i].z = vweights[i].y = 0.0f; sum += weights[i].x; } for (int i = 0; i < FILTER_SIZE; i++) { hweights[i].x /= sum; vweights[i].x /= sum; } return weights; }(); static const float4* hweights = weights; static const float4* vweights = weights + FILTER_SIZE; static Material* blur = [](Engine& engine) { std::string txt = R"SHADER( void material(inout MaterialInputs material) { prepareMaterial(material); float2 uv = gl_FragCoord.xy; vec4 c = vec4(0); for (int i = 0; i < FILTER_SIZE; i++) { float2 st = (uv + materialParams.weights[i].yz) * getResolution().zw; c += texture(materialParams_color, st) * materialParams.weights[i].x; } material.baseColor = c; } )SHADER"; const std::string from("FILTER_SIZE"); const std::string to = std::to_string(FILTER_SIZE); size_t pos = txt.find(from); txt.replace(pos, from.length(), to.c_str()); filamat::Package pkg = filamat::MaterialBuilder() .name("hblur") .material(txt.c_str()) .require(VertexAttribute::UV0) .parameter(filamat::MaterialBuilder::SamplerType::SAMPLER_2D, "color") .parameter(filamat::MaterialBuilder::UniformType::FLOAT4, FILTER_SIZE, "weights") .targetApi(filamat::MaterialBuilder::TargetApi::OPENGL) .shading(Shading::UNLIT) .depthWrite(false) .depthCulling(false) .build(); return Material::Builder().package(pkg.getData(), pkg.getSize()).build(engine); }(*engine); TextureSampler::MinFilter minFilter; TextureSampler::MagFilter magFilter; MaterialInstance* matInstance; switch (op) { case BLIT: minFilter = TextureSampler::MinFilter::LINEAR; magFilter = TextureSampler::MagFilter::LINEAR; matInstance = blit->createInstance(); break; case HBLUR: minFilter = TextureSampler::MinFilter::NEAREST; magFilter = TextureSampler::MagFilter::NEAREST; matInstance = blur->createInstance(); matInstance->setParameter("weights", hweights, FILTER_SIZE); break; case VBLUR: minFilter = TextureSampler::MinFilter::NEAREST; magFilter = TextureSampler::MagFilter::NEAREST; matInstance = blur->createInstance(); matInstance->setParameter("weights", vweights, FILTER_SIZE); break; case MIX: minFilter = TextureSampler::MinFilter::LINEAR; magFilter = TextureSampler::MagFilter::LINEAR; matInstance = mix->createInstance(); matInstance->setParameter("secondary", secondary, TextureSampler(minFilter, magFilter)); break; } matInstance->setParameter("color", tex, TextureSampler(minFilter, magFilter)); Entity entity = EntityManager::get().create(); RenderableManager::Builder(1) .boundingBox({{ 0, 0, 0 }, { 9000, 9000, 9000 }}) .material(0, matInstance) .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, vertexBuffer, indexBuffer) .build(*engine, entity); return entity; } Entity createDisk(Engine* engine, Texture* reflection) { constexpr int nslices = 64; constexpr int nverts = nslices + 2; static float4 verts[nverts]; int slice = 0; while (slice < nslices + 1) { float theta = 2.0f * M_PI * slice / nslices; verts[slice++] = float4 { std::cos(theta), std::sin(theta), 0.5 + 0.5 * std::cos(theta), 0.5 + 0.5 * std::sin(theta) }; } verts[slice] = {0.0f, 0.0f, 0.5f, 0.5f}; static uint16_t indices[nslices * 3]; for (int slice = 0, j = 0; slice < nslices; ++slice) { indices[j++] = nverts - 1; indices[j++] = slice; indices[j++] = (slice + 1) % (nslices + 1); } VertexBuffer* vbuffer = VertexBuffer::Builder() .vertexCount(nverts) .bufferCount(1) .attribute(VertexAttribute::POSITION, 0, VertexBuffer::AttributeType::FLOAT2, 0, 16) .attribute(VertexAttribute::UV0, 0, VertexBuffer::AttributeType::FLOAT2, 8, 16) .build(*engine); vbuffer->setBufferAt(*engine, 0, VertexBuffer::BufferDescriptor(verts, sizeof(verts), nullptr)); IndexBuffer* ibuffer = IndexBuffer::Builder() .indexCount(nslices * 3) .bufferType(IndexBuffer::IndexType::USHORT) .build(*engine); ibuffer->setBuffer(*engine, IndexBuffer::BufferDescriptor(indices, sizeof(indices), nullptr)); filamat::Package pkg = filamat::MaterialBuilder() .name("podium") .material(R"SHADER( void material(inout MaterialInputs material) { vec3 n = texture(materialParams_normal, getUV0()).xyz * 2.0 - 1.0; // The blue tiles normal map is very harsh, so we soften the normal vector. material.normal = normalize(n + vec3(0, 0, 5)); prepareMaterial(material); material.baseColor = texture(materialParams_color, getUV0()); material.roughness = texture(materialParams_roughness, getUV0()).r; material.ambientOcclusion = texture(materialParams_ao, getUV0()).r; float2 uv = gl_FragCoord.xy * getResolution().zw; uv.y = 1.0 - uv.y; vec3 reflection = texture(materialParams_reflection, uv).xyz; // HACK: blend the reflection with the baseColor. material.baseColor.rgb = mix(reflection, material.baseColor.rgb, 0.75); } )SHADER") .require(VertexAttribute::UV0) .parameter(filamat::MaterialBuilder::SamplerType::SAMPLER_2D, "normal") .parameter(filamat::MaterialBuilder::SamplerType::SAMPLER_2D, "color") .parameter(filamat::MaterialBuilder::SamplerType::SAMPLER_2D, "roughness") .parameter(filamat::MaterialBuilder::SamplerType::SAMPLER_2D, "ao") .parameter(filamat::MaterialBuilder::SamplerType::SAMPLER_2D, "reflection") .targetApi(filamat::MaterialBuilder::TargetApi::OPENGL) .specularAntiAliasing(true) .shading(Shading::LIT) .build(); Material* material = Material::Builder().package(pkg.getData(), pkg.getSize()).build(*engine); MaterialInstance* matInstance = material->createInstance(); auto createTexture4 = [engine](const uint8_t* data, int size, bool srgb) { int width, height, nchan; auto texels = stbi_load_from_memory(data, size, &width, &height, &nchan, 4); Texture::PixelBufferDescriptor buffer(texels, size_t(width * height * 4), Texture::Format::RGBA, Texture::Type::UBYTE, (Texture::PixelBufferDescriptor::Callback) &stbi_image_free); auto tex = Texture::Builder() .width(uint32_t(width)).height(uint32_t(height)).levels(1) .sampler(Texture::Sampler::SAMPLER_2D) .format(srgb ? Texture::InternalFormat::SRGB8_A8 : Texture::InternalFormat::RGBA8) .build(*engine); tex->setImage(*engine, 0, std::move(buffer)); tex->generateMipmaps(*engine); return tex; }; auto createTexture1 = [engine](const uint8_t* data, int size) { int width, height, nchan; auto texels = stbi_load_from_memory(data, size, &width, &height, &nchan, 1); Texture::PixelBufferDescriptor buffer(texels, size_t(width * height), Texture::Format::R, Texture::Type::UBYTE, (Texture::PixelBufferDescriptor::Callback) &stbi_image_free); auto tex = Texture::Builder() .width(uint32_t(width)).height(uint32_t(height)).levels(1) .sampler(Texture::Sampler::SAMPLER_2D) .format(Texture::InternalFormat::R8) .build(*engine); tex->setImage(*engine, 0, std::move(buffer)); tex->generateMipmaps(*engine); return tex; }; auto normal = createTexture4(RESOURCE_ARGS(BLUE_TILES_01_NORMAL), false); auto color = createTexture4(RESOURCE_ARGS(BLUE_TILES_01_COLOR), true); auto roughness = createTexture1(RESOURCE_ARGS(BLUE_TILES_01_ROUGHNESS)); auto occlusion = createTexture1(RESOURCE_ARGS(BLUE_TILES_01_AO)); TextureSampler sampler(TextureSampler::MinFilter::LINEAR_MIPMAP_LINEAR, TextureSampler::MagFilter::LINEAR); matInstance->setParameter("normal", normal, sampler); matInstance->setParameter("color", color, sampler); matInstance->setParameter("roughness", roughness, sampler); matInstance->setParameter("ao", occlusion, sampler); matInstance->setParameter("reflection", reflection, TextureSampler(TextureSampler::MagFilter::LINEAR)); auto entity = EntityManager::get().create(); RenderableManager::Builder(1) .boundingBox({{ -1, -1, 1 }, { 1, 1, 1 }}) .material(0, matInstance) .geometry(0, RenderableManager::PrimitiveType::TRIANGLES, vbuffer, ibuffer) .culling(false) .receiveShadows(true) .castShadows(false) .build(*engine, entity); auto& tcm = engine->getTransformManager(); tcm.create(entity); return entity; } mat4f fitIntoUnitCube(const Aabb& bounds) { auto minPoint = bounds.min; auto maxPoint = bounds.max; float maxExtent = 0; maxExtent = std::max(maxPoint.x - minPoint.x, maxPoint.y - minPoint.y); maxExtent = std::max(maxExtent, maxPoint.z - minPoint.z); float scaleFactor = 2.0f / maxExtent; float3 center = (minPoint + maxPoint) / 2.0f; return mat4f::scaling(float3(scaleFactor)) * mat4f::translation(-center); } } // namespace LucyUtils