PBRUtils.h#
Contains Texture utility helpers.
Includes#
PVRCore/glm.h
PVRCore/texture/Texture.h
Included By#
Namespaces#
Functions#
Source Code#
#pragma once
#include "PVRCore/glm.h"
#include "PVRCore/texture/Texture.h"
namespace pvr {
namespace utils {
namespace {
inline glm::vec2 hammersley(uint32_t i, uint32_t N)
{
// Radical inverse based on http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html
uint32_t bits = (i << 16u) | (i >> 16u);
bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
float rdi = float(bits) * static_cast<float>(2.3283064365386963e-10);
return glm::vec2(float(i) / float(N), rdi);
}
inline float G1(float k, float NoV) { return NoV / (NoV * (1.0f - k) + k); }
// Geometric Shadowing function
float gSmith(float NoL, float NoV, float roughness)
{
float k = (roughness * roughness) * 0.5f;
return G1(k, NoL) * G1(k, NoV);
}
// Sample a half-vector in world space
// Based on http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_slides.pdf
glm::vec3 importanceSampleGGX(glm::vec2 Xi, float roughness, glm::vec3 N)
{
// Maps a 2D point to a hemisphere with spread based on roughness
float a = roughness * roughness;
float phi = 2.0f * glm::pi<float>() * Xi.x;
float cosTheta = sqrt(glm::clamp((1.0f - Xi.y) / (1.0f + (a * a - 1.0f) * Xi.y), 0.0f, 1.0f));
float sinTheta = sqrt(glm::clamp(1.0f - cosTheta * cosTheta, 0.0f, 1.0f));
glm::vec3 H = glm::vec3(sinTheta * cos(phi), sinTheta * sin(phi), cosTheta);
glm::vec3 up = glm::abs(N.z) < 0.999 ? glm::vec3(0, 0, 1) : glm::vec3(1, 0, 0);
glm::vec3 tangent = glm::normalize(glm::cross(up, N));
glm::vec3 bitangent = glm::cross(N, tangent);
return glm::normalize(tangent * H.x + bitangent * H.y + N * H.z);
}
// http://blog.selfshadow.com/publications/s2013-shading-course/karis/s2013_pbs_epic_notes_v2.pdf
glm::vec2 integrateBRDF(float roughness, float NoV)
{
const glm::vec3 N = glm::vec3(0.0, 0.0, 1.0); // normal always pointing forward.
const glm::vec3 V = glm::vec3(sqrt(glm::clamp(1.0 - NoV * NoV, 0.0, 1.0)), 0.0, NoV);
float A = 0.0f;
float B = 0.0f;
uint32_t numSamples = 1024u;
for (uint32_t i = 0u; i < numSamples; ++i)
{
glm::vec2 Xi = hammersley(i, numSamples);
glm::vec3 H = importanceSampleGGX(Xi, roughness, N);
glm::vec3 L = 2.0f * glm::dot(V, H) * H - V;
float NoL = glm::max(glm::dot(N, L), 0.0f);
if (NoL > 0.0f)
{
float NoH = glm::max(glm::dot(N, H), 0.001f);
float VoH = glm::max(glm::dot(V, H), 0.001f);
float currentNoV = glm::max(glm::dot(N, V), 0.001f);
const float G = gSmith(NoL, currentNoV, roughness);
const float G_Vis = (G * VoH) / (NoH * currentNoV /*avoid division by zero*/);
const float Fc = pow(1.0f - VoH, 5.0f);
A += (1.0f - Fc) * G_Vis;
B += Fc * G_Vis;
}
}
return glm::vec2(A, B) / float(numSamples);
}
} // namespace
inline pvr::Texture generateCookTorranceBRDFLUT(uint32_t mapDim = 256)
{
pvr::TextureHeader header;
header.setWidth(mapDim);
header.setHeight(mapDim);
header.setChannelType(pvr::VariableType::SignedFloat);
header.setNumFaces(1);
header.setNumMipMapLevels(1);
header.setPixelFormat(pvr::PixelFormat::RG_1616());
pvr::Texture returnTex(header);
const uint32_t stride = sizeof(glm::detail::hdata);
const uint32_t formatStride = stride * 2;
uint32_t offset = 0;
for (uint32_t j = 0; j < mapDim; ++j) // y
{
for (uint32_t i = 0; i < mapDim; ++i) // x
{
glm::vec2 v2 = integrateBRDF((static_cast<float>(j) + .5f) / static_cast<float>(mapDim), ((static_cast<float>(i) + .5f) / static_cast<float>(mapDim)));
glm::detail::hdata halfR = glm::detail::toFloat16(v2.r);
glm::detail::hdata halfG = glm::detail::toFloat16(v2.g);
memcpy(returnTex.getDataPointer() + offset, &halfR, stride);
memcpy(returnTex.getDataPointer() + offset + stride, &halfG, stride);
offset += formatStride;
}
}
return returnTex;
}
} // namespace utils
} // namespace pvr