mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-06-23 16:49:06 +02:00
433 lines
16 KiB
C++
433 lines
16 KiB
C++
//
|
|
// SubsurfaceScattering.cpp
|
|
// libraries/render-utils/src/
|
|
//
|
|
// Created by Sam Gateau 6/3/2016.
|
|
// Copyright 2016 High Fidelity, Inc.
|
|
//
|
|
// Distributed under the Apache License, Version 2.0.
|
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
//
|
|
#include "SubsurfaceScattering.h"
|
|
|
|
#include <gpu/Context.h>
|
|
#include <gpu/StandardShaderLib.h>
|
|
|
|
#include "FramebufferCache.h"
|
|
|
|
#include "DeferredLightingEffect.h"
|
|
|
|
#include "subsurfaceScattering_makeLUT_frag.h"
|
|
#include "subsurfaceScattering_drawScattering_frag.h"
|
|
|
|
enum ScatteringShaderBufferSlots {
|
|
ScatteringTask_FrameTransformSlot = 0,
|
|
ScatteringTask_ParamSlot,
|
|
ScatteringTask_LightSlot,
|
|
};
|
|
enum ScatteringShaderMapSlots {
|
|
ScatteringTask_ScatteringTableSlot = 0,
|
|
ScatteringTask_CurvatureMapSlot,
|
|
ScatteringTask_DiffusedCurvatureMapSlot,
|
|
ScatteringTask_NormalMapSlot,
|
|
|
|
ScatteringTask_AlbedoMapSlot,
|
|
ScatteringTask_LinearMapSlot,
|
|
|
|
SCatteringTask_IBLMapSlot,
|
|
|
|
};
|
|
|
|
SubsurfaceScattering::SubsurfaceScattering() {
|
|
Parameters parameters;
|
|
_parametersBuffer = gpu::BufferView(std::make_shared<gpu::Buffer>(sizeof(Parameters), (const gpu::Byte*) ¶meters));
|
|
}
|
|
|
|
void SubsurfaceScattering::configure(const Config& config) {
|
|
auto& params = _parametersBuffer.get<Parameters>();
|
|
|
|
glm::vec4 bentInfo(config.bentRed, config.bentGreen, config.bentBlue, config.bentScale);
|
|
|
|
if (bentInfo != params.normalBentInfo) {
|
|
_parametersBuffer.edit<Parameters>().normalBentInfo = bentInfo;
|
|
}
|
|
|
|
if (config.curvatureOffset != params.curvatureInfo.x) {
|
|
_parametersBuffer.edit<Parameters>().curvatureInfo.x = config.curvatureOffset;
|
|
}
|
|
if (config.curvatureScale != params.curvatureInfo.y) {
|
|
_parametersBuffer.edit<Parameters>().curvatureInfo.y = config.curvatureScale;
|
|
}
|
|
|
|
_showLUT = config.showLUT;
|
|
}
|
|
|
|
|
|
|
|
gpu::PipelinePointer SubsurfaceScattering::getScatteringPipeline() {
|
|
if (!_scatteringPipeline) {
|
|
auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS();
|
|
// auto ps = gpu::StandardShaderLib::getDrawTextureOpaquePS();
|
|
auto ps = gpu::Shader::createPixel(std::string(subsurfaceScattering_drawScattering_frag));
|
|
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
|
|
|
|
gpu::Shader::BindingSet slotBindings;
|
|
slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), ScatteringTask_FrameTransformSlot));
|
|
slotBindings.insert(gpu::Shader::Binding(std::string("scatteringParamsBuffer"), ScatteringTask_ParamSlot));
|
|
slotBindings.insert(gpu::Shader::Binding(std::string("lightBuffer"), ScatteringTask_LightSlot));
|
|
|
|
slotBindings.insert(gpu::Shader::Binding(std::string("scatteringLUT"), ScatteringTask_ScatteringTableSlot));
|
|
slotBindings.insert(gpu::Shader::Binding(std::string("curvatureMap"), ScatteringTask_CurvatureMapSlot));
|
|
slotBindings.insert(gpu::Shader::Binding(std::string("diffusedCurvatureMap"), ScatteringTask_DiffusedCurvatureMapSlot));
|
|
slotBindings.insert(gpu::Shader::Binding(std::string("normalMap"), ScatteringTask_NormalMapSlot));
|
|
|
|
slotBindings.insert(gpu::Shader::Binding(std::string("albedoMap"), ScatteringTask_AlbedoMapSlot));
|
|
slotBindings.insert(gpu::Shader::Binding(std::string("linearDepthMap"), ScatteringTask_LinearMapSlot));
|
|
|
|
gpu::Shader::makeProgram(*program, slotBindings);
|
|
|
|
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
|
|
|
|
// Stencil test the curvature pass for objects pixels only, not the background
|
|
// state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
|
|
|
|
_scatteringPipeline = gpu::Pipeline::create(program, state);
|
|
}
|
|
|
|
return _scatteringPipeline;
|
|
}
|
|
|
|
|
|
gpu::PipelinePointer _showLUTPipeline;
|
|
gpu::PipelinePointer getShowLUTPipeline();
|
|
gpu::PipelinePointer SubsurfaceScattering::getShowLUTPipeline() {
|
|
if (!_showLUTPipeline) {
|
|
auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS();
|
|
auto ps = gpu::StandardShaderLib::getDrawTextureOpaquePS();
|
|
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
|
|
|
|
gpu::Shader::BindingSet slotBindings;
|
|
// slotBindings.insert(gpu::Shader::Binding(std::string("deferredFrameTransformBuffer"), SubsurfaceScattering_FrameTransformSlot));
|
|
// slotBindings.insert(gpu::Shader::Binding(std::string("sourceMap"), BlurTask_SourceSlot));
|
|
gpu::Shader::makeProgram(*program, slotBindings);
|
|
|
|
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
|
|
|
|
// Stencil test the curvature pass for objects pixels only, not the background
|
|
// state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
|
|
|
|
_showLUTPipeline = gpu::Pipeline::create(program, state);
|
|
}
|
|
|
|
return _showLUTPipeline;
|
|
}
|
|
|
|
bool SubsurfaceScattering::updateScatteringFramebuffer(const gpu::FramebufferPointer& sourceFramebuffer, gpu::FramebufferPointer& scatteringFramebuffer) {
|
|
if (!sourceFramebuffer) {
|
|
return false;
|
|
}
|
|
|
|
if (!_scatteringFramebuffer) {
|
|
_scatteringFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create());
|
|
|
|
// attach depthStencil if present in source
|
|
if (sourceFramebuffer->hasDepthStencil()) {
|
|
// _scatteringFramebuffer->setDepthStencilBuffer(sourceFramebuffer->getDepthStencilBuffer(), sourceFramebuffer->getDepthStencilBufferFormat());
|
|
}
|
|
auto blurringSampler = gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_LINEAR_MIP_POINT);
|
|
auto blurringTarget = gpu::TexturePointer(gpu::Texture::create2D(sourceFramebuffer->getRenderBuffer(0)->getTexelFormat(), sourceFramebuffer->getWidth(), sourceFramebuffer->getHeight(), blurringSampler));
|
|
_scatteringFramebuffer->setRenderBuffer(0, blurringTarget);
|
|
} else {
|
|
// it would be easier to just call resize on the bluredFramebuffer and let it work if needed but the source might loose it's depth buffer when doing so
|
|
if ((_scatteringFramebuffer->getWidth() != sourceFramebuffer->getWidth()) || (_scatteringFramebuffer->getHeight() != sourceFramebuffer->getHeight())) {
|
|
_scatteringFramebuffer->resize(sourceFramebuffer->getWidth(), sourceFramebuffer->getHeight(), sourceFramebuffer->getNumSamples());
|
|
if (sourceFramebuffer->hasDepthStencil()) {
|
|
// _scatteringFramebuffer->setDepthStencilBuffer(sourceFramebuffer->getDepthStencilBuffer(), sourceFramebuffer->getDepthStencilBufferFormat());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!scatteringFramebuffer) {
|
|
scatteringFramebuffer = _scatteringFramebuffer;
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
void SubsurfaceScattering::run(const render::SceneContextPointer& sceneContext, const render::RenderContextPointer& renderContext, const Inputs& inputs, gpu::FramebufferPointer& scatteringFramebuffer) {
|
|
assert(renderContext->args);
|
|
assert(renderContext->args->hasViewFrustum());
|
|
|
|
RenderArgs* args = renderContext->args;
|
|
|
|
if (!_scatteringTable) {
|
|
_scatteringTable = SubsurfaceScattering::generatePreIntegratedScattering(args);
|
|
}
|
|
|
|
|
|
auto pipeline = getScatteringPipeline();
|
|
|
|
auto& frameTransform = inputs.getFirst();
|
|
auto& curvatureFramebuffer = inputs.getSecond();
|
|
auto& diffusedFramebuffer = inputs.getThird();
|
|
|
|
|
|
auto framebufferCache = DependencyManager::get<FramebufferCache>();
|
|
|
|
if (!updateScatteringFramebuffer(curvatureFramebuffer, scatteringFramebuffer)) {
|
|
return;
|
|
}
|
|
|
|
const auto theLight = DependencyManager::get<DeferredLightingEffect>()->getLightStage().lights[0];
|
|
|
|
gpu::doInBatch(args->_context, [=](gpu::Batch& batch) {
|
|
batch.enableStereo(false);
|
|
|
|
batch.setViewportTransform(args->_viewport);
|
|
|
|
batch.setFramebuffer(_scatteringFramebuffer);
|
|
// batch.clearColorFramebuffer(gpu::Framebuffer::BUFFER_COLOR0, vec4(vec3(0), 0), false);
|
|
|
|
batch.setPipeline(pipeline);
|
|
|
|
batch.setUniformBuffer(ScatteringTask_FrameTransformSlot, frameTransform->getFrameTransformBuffer());
|
|
batch.setUniformBuffer(ScatteringTask_ParamSlot, _parametersBuffer);
|
|
if (theLight->light)
|
|
batch.setUniformBuffer(ScatteringTask_LightSlot, theLight->light->getSchemaBuffer());
|
|
batch.setResourceTexture(ScatteringTask_ScatteringTableSlot, _scatteringTable);
|
|
batch.setResourceTexture(ScatteringTask_CurvatureMapSlot, curvatureFramebuffer->getRenderBuffer(0));
|
|
batch.setResourceTexture(ScatteringTask_DiffusedCurvatureMapSlot, diffusedFramebuffer->getRenderBuffer(0));
|
|
batch.setResourceTexture(ScatteringTask_NormalMapSlot, framebufferCache->getDeferredNormalTexture());
|
|
batch.setResourceTexture(ScatteringTask_AlbedoMapSlot, framebufferCache->getDeferredColorTexture());
|
|
batch.setResourceTexture(ScatteringTask_LinearMapSlot, framebufferCache->getDepthPyramidTexture());
|
|
|
|
|
|
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
|
|
|
if (_showLUT) {
|
|
auto viewportSize = std::min(args->_viewport.z, args->_viewport.w) >> 1;
|
|
batch.setViewportTransform(glm::ivec4(0, 0, viewportSize, viewportSize));
|
|
batch.setPipeline(getShowLUTPipeline());
|
|
batch.setResourceTexture(0, _scatteringTable);
|
|
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Reference: http://www.altdevblogaday.com/2011/12/31/skin-shading-in-unity3d/
|
|
|
|
#include <cstdio>
|
|
#include <cmath>
|
|
#include <algorithm>
|
|
|
|
#define _PI 3.14159265358979523846
|
|
|
|
using namespace std;
|
|
|
|
|
|
double gaussian(float v, float r) {
|
|
double g = (1.0 / sqrt(2.0 * _PI * v)) * exp(-(r*r) / (2.0 * v));
|
|
return g;
|
|
}
|
|
|
|
vec3 scatter(double r) {
|
|
// Values from GPU Gems 3 "Advanced Skin Rendering".
|
|
// Originally taken from real life samples.
|
|
static const double profile[][4] = {
|
|
{ 0.0064, 0.233, 0.455, 0.649 },
|
|
{ 0.0484, 0.100, 0.336, 0.344 },
|
|
{ 0.1870, 0.118, 0.198, 0.000 },
|
|
{ 0.5670, 0.113, 0.007, 0.007 },
|
|
{ 1.9900, 0.358, 0.004, 0.000 },
|
|
{ 7.4100, 0.078, 0.000, 0.000 }
|
|
};
|
|
static const int profileNum = 6;
|
|
vec3 ret(0.0);
|
|
for (int i = 0; i < profileNum; i++) {
|
|
double g = gaussian(profile[i][0] * 1.414f, r);
|
|
ret.x += g * profile[i][1];
|
|
ret.y += g * profile[i][2];
|
|
ret.z += g * profile[i][3];
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
vec3 integrate(double cosTheta, double skinRadius) {
|
|
// Angle from lighting direction.
|
|
double theta = acos(cosTheta);
|
|
vec3 totalWeights(0.0);
|
|
vec3 totalLight(0.0);
|
|
vec3 skinColour(1.0);
|
|
|
|
double a = -(_PI);
|
|
|
|
double inc = 0.005;
|
|
|
|
while (a <= (_PI)) {
|
|
double sampleAngle = theta + a;
|
|
double diffuse = cos(sampleAngle);
|
|
if (diffuse < 0.0) diffuse = 0.0;
|
|
if (diffuse > 1.0) diffuse = 1.0;
|
|
|
|
// Distance.
|
|
double sampleDist = abs(2.0 * skinRadius * sin(a * 0.5));
|
|
|
|
// Profile Weight.
|
|
vec3 weights = scatter(sampleDist);
|
|
|
|
totalWeights += weights;
|
|
totalLight.x += diffuse * weights.x * (skinColour.x * skinColour.x);
|
|
totalLight.y += diffuse * weights.y * (skinColour.y * skinColour.y);
|
|
totalLight.z += diffuse * weights.z * (skinColour.z * skinColour.z);
|
|
a += inc;
|
|
}
|
|
|
|
vec3 result;
|
|
result.x = totalLight.x / totalWeights.x;
|
|
result.y = totalLight.y / totalWeights.y;
|
|
result.z = totalLight.z / totalWeights.z;
|
|
|
|
return result;
|
|
}
|
|
|
|
void diffuseScatter(gpu::TexturePointer& lut) {
|
|
int width = lut->getWidth();
|
|
int height = lut->getHeight();
|
|
|
|
const int COMPONENT_COUNT = 4;
|
|
std::vector<unsigned char> bytes(COMPONENT_COUNT * height * width);
|
|
|
|
int index = 0;
|
|
for (int j = 0; j < height; j++) {
|
|
for (int i = 0; i < width; i++) {
|
|
// Lookup by: x: NDotL y: 1 / r
|
|
float y = 2.0 * 1.0 / ((j + 1.0) / (double)height);
|
|
float x = ((i / (double)width) * 2.0) - 1.0;
|
|
vec3 val = integrate(x, y);
|
|
|
|
// Convert to linear
|
|
val.x = sqrt(val.x);
|
|
val.y = sqrt(val.y);
|
|
val.z = sqrt(val.z);
|
|
|
|
// Convert to 24-bit image.
|
|
unsigned char valI[3];
|
|
if (val.x > 1.0) val.x = 1.0;
|
|
if (val.y > 1.0) val.y = 1.0;
|
|
if (val.z > 1.0) val.z = 1.0;
|
|
valI[0] = (unsigned char)(val.x * 256.0);
|
|
valI[1] = (unsigned char)(val.y * 256.0);
|
|
valI[2] = (unsigned char)(val.z * 256.0);
|
|
|
|
bytes[COMPONENT_COUNT * index] = valI[0];
|
|
bytes[COMPONENT_COUNT * index + 1] = valI[1];
|
|
bytes[COMPONENT_COUNT * index + 2] = valI[2];
|
|
bytes[COMPONENT_COUNT * index + 3] = 255.0;
|
|
|
|
index++;
|
|
}
|
|
}
|
|
|
|
lut->assignStoredMip(0, gpu::Element::COLOR_RGBA_32, bytes.size(), bytes.data());
|
|
}
|
|
|
|
|
|
void diffuseScatterGPU(gpu::TexturePointer& profileMap, gpu::TexturePointer& lut, RenderArgs* args) {
|
|
int width = lut->getWidth();
|
|
int height = lut->getHeight();
|
|
|
|
gpu::PipelinePointer makePipeline;
|
|
{
|
|
auto vs = gpu::StandardShaderLib::getDrawUnitQuadTexcoordVS();
|
|
auto ps = gpu::Shader::createPixel(std::string(subsurfaceScattering_makeLUT_frag));
|
|
gpu::ShaderPointer program = gpu::Shader::createProgram(vs, ps);
|
|
|
|
gpu::Shader::BindingSet slotBindings;
|
|
slotBindings.insert(gpu::Shader::Binding(std::string("profileMap"), 0));
|
|
// slotBindings.insert(gpu::Shader::Binding(std::string("sourceMap"), BlurTask_SourceSlot));
|
|
gpu::Shader::makeProgram(*program, slotBindings);
|
|
|
|
gpu::StatePointer state = gpu::StatePointer(new gpu::State());
|
|
|
|
// Stencil test the curvature pass for objects pixels only, not the background
|
|
// state->setStencilTest(true, 0xFF, gpu::State::StencilTest(0, 0xFF, gpu::NOT_EQUAL, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP, gpu::State::STENCIL_OP_KEEP));
|
|
|
|
makePipeline = gpu::Pipeline::create(program, state);
|
|
}
|
|
|
|
auto makeFramebuffer = gpu::FramebufferPointer(gpu::Framebuffer::create());
|
|
makeFramebuffer->setRenderBuffer(0, lut);
|
|
|
|
gpu::doInBatch(args->_context, [=](gpu::Batch& batch) {
|
|
batch.enableStereo(false);
|
|
|
|
batch.setViewportTransform(glm::ivec4(0, 0, width, height));
|
|
|
|
batch.setFramebuffer(makeFramebuffer);
|
|
batch.setPipeline(makePipeline);
|
|
batch.setResourceTexture(0, profileMap);
|
|
batch.draw(gpu::TRIANGLE_STRIP, 4);
|
|
batch.setResourceTexture(0, nullptr);
|
|
batch.setPipeline(nullptr);
|
|
batch.setFramebuffer(nullptr);
|
|
|
|
});
|
|
}
|
|
|
|
void diffuseProfile(gpu::TexturePointer& profile) {
|
|
int width = profile->getWidth();
|
|
int height = profile->getHeight();
|
|
|
|
const int COMPONENT_COUNT = 4;
|
|
std::vector<unsigned char> bytes(COMPONENT_COUNT * height * width);
|
|
|
|
int index = 0;
|
|
for (int j = 0; j < height; j++) {
|
|
for (int i = 0; i < width; i++) {
|
|
float y = (double)(i + 1.0) / (double)width;
|
|
vec3 val = scatter(y * 2.0f);
|
|
|
|
// Convert to 24-bit image.
|
|
unsigned char valI[3];
|
|
if (val.x > 1.0) val.x = 1.0;
|
|
if (val.y > 1.0) val.y = 1.0;
|
|
if (val.z > 1.0) val.z = 1.0;
|
|
valI[0] = (unsigned char)(val.x * 255.0);
|
|
valI[1] = (unsigned char)(val.y * 255.0);
|
|
valI[2] = (unsigned char)(val.z * 255.0);
|
|
|
|
bytes[COMPONENT_COUNT * index] = valI[0];
|
|
bytes[COMPONENT_COUNT * index + 1] = valI[1];
|
|
bytes[COMPONENT_COUNT * index + 2] = valI[2];
|
|
bytes[COMPONENT_COUNT * index + 3] = 255.0;
|
|
|
|
index++;
|
|
}
|
|
}
|
|
|
|
|
|
profile->assignStoredMip(0, gpu::Element::COLOR_RGBA_32, bytes.size(), bytes.data());
|
|
}
|
|
|
|
|
|
|
|
gpu::TexturePointer SubsurfaceScattering::generatePreIntegratedScattering(RenderArgs* args) {
|
|
|
|
auto profileMap = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32, 128, 1, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
|
|
diffuseProfile(profileMap);
|
|
|
|
const int WIDTH = 128;
|
|
const int HEIGHT = 128;
|
|
auto scatteringLUT = gpu::TexturePointer(gpu::Texture::create2D(gpu::Element::COLOR_RGBA_32, WIDTH, HEIGHT, gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR)));
|
|
diffuseScatter(scatteringLUT);
|
|
//diffuseScatterGPU(profileMap, scatteringLUT, args);
|
|
return scatteringLUT;
|
|
}
|
|
|