cuda-samples/Samples/5_Domain_Specific/smokeParticles/SmokeRenderer.cpp
2022-01-13 11:35:24 +05:30

568 lines
17 KiB
C++

/* Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* * Neither the name of NVIDIA CORPORATION nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
This class renders particles using OpenGL and GLSL shaders
*/
#include <math.h>
#include <stdlib.h>
#define HELPERGL_EXTERN_GL_FUNC_IMPLEMENTATION
#include <helper_gl.h>
#include "SmokeRenderer.h"
#include "SmokeShaders.h"
#if defined(__APPLE__) || defined(MACOSX)
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
#include <GLUT/glut.h>
#else
#include <GL/freeglut.h>
#endif
#define USE_MBLUR 1
#define COLOR_ATTENUATION 1
SmokeRenderer::SmokeRenderer(int maxParticles)
: mMaxParticles(maxParticles),
mNumParticles(0),
mPosVbo(0),
mVelVbo(0),
mColorVbo(0),
mIndexBuffer(0),
mParticleRadius(0.005f),
mDisplayMode(VOLUMETRIC),
mWindowW(800),
mWindowH(600),
mFov(40.0f),
m_downSample(2),
m_numSlices(32),
m_numDisplayedSlices(32),
m_sliceNo(0),
m_shadowAlpha(0.005f),
m_spriteAlpha(0.1f),
m_doBlur(false),
m_blurRadius(1.0f),
m_displayLightBuffer(false),
m_lightPos(5.0f, 5.0f, -5.0f),
m_lightTarget(0.0f, 0.0f, 0.0f),
m_lightColor(1.0f, 1.0f, 0.5f),
m_colorAttenuation(0.1f, 0.2f, 0.3f),
m_lightBufferSize(256),
m_srcLightTexture(0),
m_lightDepthTexture(0),
m_lightFbo(0),
m_imageTex(0),
m_depthTex(0),
m_imageFbo(0) {
// load shader programs
m_simpleProg = new GLSLProgram(particleVS, simplePS);
m_particleProg = new GLSLProgram(mblurVS, mblurGS, particlePS);
m_particleShadowProg = new GLSLProgram(mblurVS, mblurGS, particleShadowPS);
m_blurProg = new GLSLProgram(passThruVS, blurPS);
m_displayTexProg = new GLSLProgram(passThruVS, texture2DPS);
// create buffer for light shadows
createLightBuffer();
glutReportErrors();
}
SmokeRenderer::~SmokeRenderer() {
delete m_particleProg;
delete m_particleShadowProg;
delete m_blurProg;
delete m_displayTexProg;
delete m_simpleProg;
delete m_lightFbo;
glDeleteTextures(2, m_lightTexture);
glDeleteTextures(1, &m_lightDepthTexture);
delete m_imageFbo;
glDeleteTextures(1, &m_imageTex);
glDeleteTextures(1, &m_depthTex);
}
// draw points from vertex buffer objects
void SmokeRenderer::drawPoints(int start, int count, bool sort) {
glBindBuffer(GL_ARRAY_BUFFER, mPosVbo);
glVertexPointer(4, GL_FLOAT, 0, 0);
glEnableClientState(GL_VERTEX_ARRAY);
if (mColorVbo) {
glBindBuffer(GL_ARRAY_BUFFER, mColorVbo);
glColorPointer(4, GL_FLOAT, 0, 0);
glEnableClientState(GL_COLOR_ARRAY);
}
if (mVelVbo) {
glBindBuffer(GL_ARRAY_BUFFER, mVelVbo);
glClientActiveTexture(GL_TEXTURE0);
glTexCoordPointer(4, GL_FLOAT, 0, 0);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
}
if (sort) {
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB, mIndexBuffer);
glDrawElements(GL_POINTS, count, GL_UNSIGNED_INT,
(void *)(start * sizeof(unsigned int)));
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
} else {
glDrawArrays(GL_POINTS, start, count);
}
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glClientActiveTexture(GL_TEXTURE0);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}
// draw points using given shader program
void SmokeRenderer::drawPointSprites(GLSLProgram *prog, int start, int count,
bool shadowed) {
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE); // don't write depth
glEnable(GL_BLEND);
prog->enable();
prog->setUniform1f("pointRadius", mParticleRadius);
if (shadowed) {
prog->bindTexture("shadowTex", m_lightTexture[m_srcLightTexture],
GL_TEXTURE_2D, 0);
}
// draw points
drawPoints(start, count, true);
prog->disable();
glDepthMask(GL_TRUE);
glDisable(GL_BLEND);
}
// calculate vectors for half-angle slice rendering
void SmokeRenderer::calcVectors() {
// get model view matrix
glGetFloatv(GL_MODELVIEW_MATRIX, (float *)m_modelView.get_value());
// calculate eye space light vector
m_lightVector = normalize(m_lightPos);
m_lightPosEye = m_modelView * vec4f(m_lightPos, 1.0);
// calculate half-angle vector between view and light
m_viewVector = -vec3f(m_modelView.get_row(2));
if (dot(m_viewVector, m_lightVector) > 0) {
m_halfVector = normalize(m_viewVector + m_lightVector);
m_invertedView = false;
} else {
m_halfVector = normalize(-m_viewVector + m_lightVector);
m_invertedView = true;
}
// calculate light view matrix
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
gluLookAt(m_lightPos[0], m_lightPos[1], m_lightPos[2], m_lightTarget[0],
m_lightTarget[1], m_lightTarget[2], 0.0, 1.0, 0.0);
// calculate light projection matrix
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
gluPerspective(45.0, 1.0, 1.0, 200.0);
glGetFloatv(GL_MODELVIEW_MATRIX, (float *)m_lightView.get_value());
glGetFloatv(GL_PROJECTION_MATRIX, (float *)m_lightProj.get_value());
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
// construct shadow matrix
matrix4f scale;
scale.set_scale(vec3f(0.5, 0.5, 0.5));
matrix4f translate;
translate.set_translate(vec3f(0.5, 0.5, 0.5));
m_shadowMatrix =
translate * scale * m_lightProj * m_lightView * inverse(m_modelView);
// calc object space eye position
m_eyePos = inverse(m_modelView) * vec4f(0.0, 0.0, 0.0, 1.0);
// calc half vector in eye space
m_halfVectorEye = m_modelView * vec4f(m_halfVector, 0.0);
}
// draw slice of particles from camera view
void SmokeRenderer::drawSlice(int i) {
m_imageFbo->Bind();
glViewport(0, 0, m_imageW, m_imageH);
glColor4f(1.0, 1.0, 1.0, m_spriteAlpha);
if (m_invertedView) {
// front-to-back
glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ONE);
} else {
// back-to-front
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
}
drawPointSprites(m_particleShadowProg, i * m_batchSize, m_batchSize, true);
m_imageFbo->Disable();
}
// draw slice of particles from light's point of view
void SmokeRenderer::drawSliceLightView(int i) {
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadMatrixf((GLfloat *)m_lightView.get_value());
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadMatrixf((GLfloat *)m_lightProj.get_value());
m_lightFbo->Bind();
glViewport(0, 0, m_lightBufferSize, m_lightBufferSize);
glColor4f(m_colorAttenuation[0] * m_shadowAlpha,
m_colorAttenuation[1] * m_shadowAlpha,
m_colorAttenuation[2] * m_shadowAlpha, 1.0);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR);
drawPointSprites(m_particleProg, i * m_batchSize, m_batchSize, false);
m_lightFbo->Disable();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
}
// draw particles as slices with shadowing
void SmokeRenderer::drawSlices() {
m_batchSize = mNumParticles / m_numSlices;
// clear light buffer
m_srcLightTexture = 0;
m_lightFbo->Bind();
m_lightFbo->AttachTexture(GL_TEXTURE_2D, m_lightTexture[m_srcLightTexture],
GL_COLOR_ATTACHMENT0_EXT);
glClearColor(1.0f - m_lightColor[0], 1.0f - m_lightColor[1],
1.0f - m_lightColor[2], 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
m_lightFbo->Disable();
// clear volume image
m_imageFbo->Bind();
glClearColor(0.0, 0.0, 0.0, 0.0);
glClear(GL_COLOR_BUFFER_BIT);
m_imageFbo->Disable();
glActiveTexture(GL_TEXTURE0);
glMatrixMode(GL_TEXTURE);
glLoadMatrixf((GLfloat *)m_shadowMatrix.get_value());
// render slices
if (m_numDisplayedSlices > m_numSlices) m_numDisplayedSlices = m_numSlices;
for (int i = 0; i < m_numDisplayedSlices; i++) {
// draw slice from camera view, sampling light buffer
drawSlice(i);
// draw slice from light view to light buffer, accumulating shadows
drawSliceLightView(i);
if (m_doBlur) {
blurLightBuffer();
}
}
glActiveTexture(GL_TEXTURE0);
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
}
// blur light buffer to simulate scattering effects
void SmokeRenderer::blurLightBuffer() {
m_lightFbo->Bind();
m_lightFbo->AttachTexture(GL_TEXTURE_2D,
m_lightTexture[1 - m_srcLightTexture],
GL_COLOR_ATTACHMENT0_EXT);
glViewport(0, 0, m_lightBufferSize, m_lightBufferSize);
m_blurProg->enable();
m_blurProg->bindTexture("tex", m_lightTexture[m_srcLightTexture],
GL_TEXTURE_2D, 0);
m_blurProg->setUniform2f("texelSize", 1.0f / (float)m_lightBufferSize,
1.0f / (float)m_lightBufferSize);
m_blurProg->setUniform1f("blurRadius", m_blurRadius);
glDisable(GL_DEPTH_TEST);
drawQuad();
m_blurProg->disable();
m_srcLightTexture = 1 - m_srcLightTexture;
m_lightFbo->Disable();
}
// display texture to screen
void SmokeRenderer::displayTexture(GLuint tex) {
m_displayTexProg->enable();
m_displayTexProg->bindTexture("tex", tex, GL_TEXTURE_2D, 0);
drawQuad();
m_displayTexProg->disable();
}
// composite final volume image on top of scene
void SmokeRenderer::compositeResult() {
glViewport(0, 0, mWindowW, mWindowH);
glDisable(GL_DEPTH_TEST);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
displayTexture(m_imageTex);
glDisable(GL_BLEND);
}
void SmokeRenderer::render() {
switch (mDisplayMode) {
case POINTS:
glColor3f(1.0, 1.0, 1.0);
m_simpleProg->enable();
drawPoints(0, mNumParticles, false);
m_simpleProg->disable();
break;
case SPRITES:
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_COLOR);
glColor4f(1.0, 1.0, 1.0, m_spriteAlpha);
drawPointSprites(m_particleProg, 0, mNumParticles, false);
break;
case VOLUMETRIC:
drawSlices();
compositeResult();
break;
case NUM_MODES:
break;
}
if (m_displayLightBuffer) {
// display light buffer to screen
glViewport(0, 0, m_lightBufferSize, m_lightBufferSize);
glDisable(GL_DEPTH_TEST);
displayTexture(m_lightTexture[m_srcLightTexture]);
glViewport(0, 0, mWindowW, mWindowH);
}
glutReportErrors();
}
// render scene depth to texture
// (this is to ensure that particle are correctly occluded in the low-resolution
// render buffer)
void SmokeRenderer::beginSceneRender(Target target) {
if (target == LIGHT_BUFFER) {
m_lightFbo->Bind();
glViewport(0, 0, m_lightBufferSize, m_lightBufferSize);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadMatrixf((GLfloat *)m_lightView.get_value());
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadMatrixf((GLfloat *)m_lightProj.get_value());
} else {
m_imageFbo->Bind();
glViewport(0, 0, m_imageW, m_imageH);
}
glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
glDepthMask(GL_TRUE);
glClear(GL_DEPTH_BUFFER_BIT);
}
void SmokeRenderer::endSceneRender(Target target) {
if (target == LIGHT_BUFFER) {
m_lightFbo->Disable();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
glPopMatrix();
} else {
m_imageFbo->Disable();
}
glViewport(0, 0, mWindowW, mWindowH);
glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
}
// create an OpenGL texture
GLuint SmokeRenderer::createTexture(GLenum target, int w, int h,
GLint internalformat, GLenum format) {
GLuint texid;
glGenTextures(1, &texid);
glBindTexture(target, texid);
glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(target, 0, internalformat, w, h, 0, format, GL_FLOAT, 0);
return texid;
}
// create buffers for off-screen rendering
void SmokeRenderer::createBuffers(int w, int h) {
if (m_imageFbo) {
glDeleteTextures(1, &m_imageTex);
glDeleteTextures(1, &m_depthTex);
delete m_imageFbo;
}
mWindowW = w;
mWindowH = h;
m_imageW = w / m_downSample;
m_imageH = h / m_downSample;
// create fbo for image buffer
GLint format = GL_RGBA16F_ARB;
// GLint format = GL_LUMINANCE16F_ARB;
// GLint format = GL_RGBA8;
m_imageTex =
createTexture(GL_TEXTURE_2D, m_imageW, m_imageH, format, GL_RGBA);
m_depthTex = createTexture(GL_TEXTURE_2D, m_imageW, m_imageH,
GL_DEPTH_COMPONENT24_ARB, GL_DEPTH_COMPONENT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
m_imageFbo = new FramebufferObject();
m_imageFbo->AttachTexture(GL_TEXTURE_2D, m_imageTex,
GL_COLOR_ATTACHMENT0_EXT);
m_imageFbo->AttachTexture(GL_TEXTURE_2D, m_depthTex, GL_DEPTH_ATTACHMENT_EXT);
m_imageFbo->IsValid();
}
void SmokeRenderer::setLightColor(vec3f c) {
m_lightColor = c;
// set light texture border color
GLfloat borderColor[4] = {1.0f - m_lightColor[0], 1.0f - m_lightColor[1],
1.0f - m_lightColor[2], 0.0f};
glBindTexture(GL_TEXTURE_2D, m_lightTexture[0]);
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
glBindTexture(GL_TEXTURE_2D, m_lightTexture[1]);
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
glBindTexture(GL_TEXTURE_2D, 0);
}
// create FBOs for light buffer
void SmokeRenderer::createLightBuffer() {
GLint format = GL_RGBA16F_ARB;
// GLint format = GL_RGBA8;
// GLint format = GL_LUMINANCE16F_ARB;
m_lightTexture[0] = createTexture(GL_TEXTURE_2D, m_lightBufferSize,
m_lightBufferSize, format, GL_RGBA);
// make shadows clamp to light color at edges
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
m_lightTexture[1] = createTexture(GL_TEXTURE_2D, m_lightBufferSize,
m_lightBufferSize, format, GL_RGBA);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
m_lightDepthTexture =
createTexture(GL_TEXTURE_2D, m_lightBufferSize, m_lightBufferSize,
GL_DEPTH_COMPONENT24_ARB, GL_DEPTH_COMPONENT);
m_lightFbo = new FramebufferObject();
m_lightFbo->AttachTexture(GL_TEXTURE_2D, m_lightTexture[m_srcLightTexture],
GL_COLOR_ATTACHMENT0_EXT);
m_lightFbo->AttachTexture(GL_TEXTURE_2D, m_lightDepthTexture,
GL_DEPTH_ATTACHMENT_EXT);
m_lightFbo->IsValid();
}
void SmokeRenderer::setWindowSize(int w, int h) {
mAspect = (float)mWindowW / (float)mWindowH;
mInvFocalLen = tan(mFov * 0.5f * NV_PI / 180.0f);
createBuffers(w, h);
}
void SmokeRenderer::drawQuad() {
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 0.0f);
glVertex2f(-1.0f, -1.0f);
glTexCoord2f(1.0f, 0.0f);
glVertex2f(1.0f, -1.0f);
glTexCoord2f(1.0f, 1.0f);
glVertex2f(1.0f, 1.0f);
glTexCoord2f(0.0f, 1.0f);
glVertex2f(-1.0f, 1.0f);
glEnd();
}
void SmokeRenderer::drawVector(vec3f v) {
glBegin(GL_LINES);
glVertex3f(0.0f, 0.0f, 0.0f);
glVertex3fv((float *)&v[0]);
glEnd();
}
// render vectors to screen for debugging
void SmokeRenderer::debugVectors() {
glColor3f(1.0f, 1.0f, 0.0f);
drawVector(m_lightVector);
glColor3f(0.0f, 1.0f, 0.0f);
drawVector(m_viewVector);
glColor3f(0.0f, 0.0f, 1.0f);
drawVector(-m_viewVector);
glColor3f(1.0f, 0.0f, 0.0f);
drawVector(m_halfVector);
}