// // 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 #include #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(sizeof(Parameters), (const gpu::Byte*) ¶meters)); } void SubsurfaceScattering::configure(const Config& config) { auto& params = _parametersBuffer.get(); glm::vec4 bentInfo(config.bentRed, config.bentGreen, config.bentBlue, config.bentScale); if (bentInfo != params.normalBentInfo) { _parametersBuffer.edit().normalBentInfo = bentInfo; } if (config.curvatureOffset != params.curvatureInfo.x) { _parametersBuffer.edit().curvatureInfo.x = config.curvatureOffset; } if (config.curvatureScale != params.curvatureInfo.y) { _parametersBuffer.edit().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(); if (!updateScatteringFramebuffer(curvatureFramebuffer, scatteringFramebuffer)) { return; } const auto theLight = DependencyManager::get()->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 #include #include #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 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 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; }