From 72500630b014f5f45131253d67d3b24ea002fd49 Mon Sep 17 00:00:00 2001 From: Andrzej Kapolka Date: Wed, 20 Aug 2014 19:26:06 -0700 Subject: [PATCH] Basic splatting. --- .../shaders/metavoxel_heightfield_splat.frag | 16 +++- .../shaders/metavoxel_heightfield_splat.vert | 19 ++++- interface/src/MetavoxelSystem.cpp | 63 ++++++++++++++-- interface/src/MetavoxelSystem.h | 4 + .../metavoxels/src/AttributeRegistry.cpp | 74 ++++++++++++------- libraries/metavoxels/src/AttributeRegistry.h | 1 - 6 files changed, 138 insertions(+), 39 deletions(-) diff --git a/interface/resources/shaders/metavoxel_heightfield_splat.frag b/interface/resources/shaders/metavoxel_heightfield_splat.frag index 7b73cf331b..6a3c058b80 100644 --- a/interface/resources/shaders/metavoxel_heightfield_splat.frag +++ b/interface/resources/shaders/metavoxel_heightfield_splat.frag @@ -11,10 +11,18 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// the diffuse texture -uniform sampler2D diffuseMap; +const int SPLAT_COUNT = 4; + +// the splat textures +uniform sampler2D diffuseMaps[SPLAT_COUNT]; + +// alpha values for the four splat textures +varying vec4 alphaValues; void main(void) { - // compute the base color based on OpenGL lighting model - gl_FragColor = gl_Color * texture2D(diffuseMap, gl_TexCoord[0].st); + // blend the splat textures + gl_FragColor = gl_Color * (texture2D(diffuseMaps[0], gl_TexCoord[0].st) * alphaValues.x + + texture2D(diffuseMaps[1], gl_TexCoord[0].st) * alphaValues.y + + texture2D(diffuseMaps[2], gl_TexCoord[0].st) * alphaValues.z + + texture2D(diffuseMaps[3], gl_TexCoord[0].st) * alphaValues.w); } diff --git a/interface/resources/shaders/metavoxel_heightfield_splat.vert b/interface/resources/shaders/metavoxel_heightfield_splat.vert index 0f36605454..950162073e 100644 --- a/interface/resources/shaders/metavoxel_heightfield_splat.vert +++ b/interface/resources/shaders/metavoxel_heightfield_splat.vert @@ -14,20 +14,37 @@ // the height texture uniform sampler2D heightMap; +// the texture that contains the texture indices +uniform sampler2D textureMap; + // the distance between height points in texture space uniform float heightScale; // the scale between height and texture textures uniform float textureScale; +// the lower bounds of the values corresponding to the splat textures +uniform vec4 textureValueMinima; + +// the upper bounds of the values corresponding to the splat textures +uniform vec4 textureValueMaxima; + +// alpha values for the four splat textures +varying vec4 alphaValues; + void main(void) { // add the height to the position float height = texture2D(heightMap, gl_MultiTexCoord0.st).r; gl_Position = gl_ModelViewProjectionMatrix * (gl_Vertex + vec4(0.0, height, 0.0, 0.0)); // the zero height should be invisible - gl_FrontColor = vec4(1.0, 1.0, 1.0, step(height, 0.0)); + gl_FrontColor = vec4(1.0, 1.0, 1.0, 1.0 - step(height, 0.0)); // pass along the scaled/offset texture coordinates gl_TexCoord[0] = (gl_MultiTexCoord0 - vec4(heightScale, heightScale, 0.0, 0.0)) * textureScale; + + // compute the alpha values for each texture + float value = texture2D(textureMap, gl_TexCoord[0].st).r; + vec4 valueVector = vec4(value, value, value, value); + alphaValues = step(textureValueMinima, valueVector) * step(valueVector, textureValueMaxima); } diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index 41ae254c71..18d76b1543 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -673,13 +673,17 @@ public: glm::vec3 vertex; }; +const int SPLAT_COUNT = 4; +const GLint SPLAT_TEXTURE_UNITS[] = { 3, 4, 5, 6 }; + void HeightfieldBuffer::render(bool cursor) { // initialize textures, etc. on first render if (_heightTextureID == 0) { glGenTextures(1, &_heightTextureID); glBindTexture(GL_TEXTURE_2D, _heightTextureID); glPixelStorei(GL_UNPACK_ALIGNMENT, 1); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexImage2D(GL_TEXTURE_2D, 0, GL_LUMINANCE, _heightSize, _heightSize, 0, @@ -702,7 +706,8 @@ void HeightfieldBuffer::render(bool cursor) { if (!_texture.isEmpty()) { glGenTextures(1, &_textureTextureID); glBindTexture(GL_TEXTURE_2D, _textureTextureID); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); int textureSize = glm::sqrt(_texture.size()); @@ -800,11 +805,53 @@ void HeightfieldBuffer::render(bool cursor) { glDrawRangeElements(GL_TRIANGLES, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0); glDepthFunc(GL_LEQUAL); + glDepthMask(false); glEnable(GL_BLEND); - glBlendFunc(GL_DST_COLOR, GL_ZERO); + glDisable(GL_ALPHA_TEST); glEnable(GL_POLYGON_OFFSET_FILL); glPolygonOffset(-1.0f, -1.0f); + + DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().bind(); + DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getSplatHeightScaleLocation(), 1.0f / _heightSize); + DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getSplatTextureScaleLocation(), (float)_heightSize / innerSize); + + glBindTexture(GL_TEXTURE_2D, _textureTextureID); + const int TEXTURES_PER_SPLAT = 4; + for (int i = 0; i < _textures.size(); i += TEXTURES_PER_SPLAT) { + for (int j = 0; j < SPLAT_COUNT; j++) { + glActiveTexture(GL_TEXTURE0 + SPLAT_TEXTURE_UNITS[j]); + int index = i + j; + if (index < _networkTextures.size()) { + const NetworkTexturePointer& texture = _networkTextures.at(index); + glBindTexture(GL_TEXTURE_2D, texture ? texture->getID() : 0); + } else { + glBindTexture(GL_TEXTURE_2D, 0); + } + } + const float QUARTER_STEP = 0.25f * EIGHT_BIT_MAXIMUM_RECIPROCAL; + DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getSplatTextureValueMinimaLocation(), + (i + 1) * EIGHT_BIT_MAXIMUM_RECIPROCAL - QUARTER_STEP, (i + 2) * EIGHT_BIT_MAXIMUM_RECIPROCAL - QUARTER_STEP, + (i + 3) * EIGHT_BIT_MAXIMUM_RECIPROCAL - QUARTER_STEP, (i + 4) * EIGHT_BIT_MAXIMUM_RECIPROCAL - QUARTER_STEP); + DefaultMetavoxelRendererImplementation::getSplatHeightfieldProgram().setUniformValue( + DefaultMetavoxelRendererImplementation::getSplatTextureValueMaximaLocation(), + (i + 1) * EIGHT_BIT_MAXIMUM_RECIPROCAL + QUARTER_STEP, (i + 2) * EIGHT_BIT_MAXIMUM_RECIPROCAL + QUARTER_STEP, + (i + 3) * EIGHT_BIT_MAXIMUM_RECIPROCAL + QUARTER_STEP, (i + 4) * EIGHT_BIT_MAXIMUM_RECIPROCAL + QUARTER_STEP); + glDrawRangeElements(GL_TRIANGLES, 0, vertexCount - 1, indexCount, GL_UNSIGNED_INT, 0); + } + + glEnable(GL_ALPHA_TEST); + glBlendFunc(GL_DST_COLOR, GL_ZERO); + + for (int i = 0; i < SPLAT_COUNT; i++) { + glActiveTexture(GL_TEXTURE0 + SPLAT_TEXTURE_UNITS[i]); + glBindTexture(GL_TEXTURE_2D, 0); + } + + glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, 0); if (Menu::getInstance()->isOptionChecked(MenuOption::SimpleShadows)) { @@ -831,7 +878,8 @@ void HeightfieldBuffer::render(bool cursor) { glDisable(GL_BLEND); glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); glDepthFunc(GL_LESS); - + glDepthMask(true); + glActiveTexture(GL_TEXTURE0); } else { @@ -992,9 +1040,12 @@ void DefaultMetavoxelRendererImplementation::init() { _splatHeightfieldProgram.bind(); _splatHeightfieldProgram.setUniformValue("heightMap", 0); - _splatHeightfieldProgram.setUniformValue("diffuseMap", 1); + _splatHeightfieldProgram.setUniformValue("textureMap", 1); + _splatHeightfieldProgram.setUniformValueArray("diffuseMaps", SPLAT_TEXTURE_UNITS, SPLAT_COUNT); _splatHeightScaleLocation = _splatHeightfieldProgram.uniformLocation("heightScale"); _splatTextureScaleLocation = _splatHeightfieldProgram.uniformLocation("textureScale"); + _splatTextureValueMinimaLocation = _splatHeightfieldProgram.uniformLocation("textureValueMinima"); + _splatTextureValueMaximaLocation = _splatHeightfieldProgram.uniformLocation("textureValueMaxima"); _splatHeightfieldProgram.release(); _lightHeightfieldProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + @@ -1638,6 +1689,8 @@ int DefaultMetavoxelRendererImplementation::_baseColorScaleLocation; ProgramObject DefaultMetavoxelRendererImplementation::_splatHeightfieldProgram; int DefaultMetavoxelRendererImplementation::_splatHeightScaleLocation; int DefaultMetavoxelRendererImplementation::_splatTextureScaleLocation; +int DefaultMetavoxelRendererImplementation::_splatTextureValueMinimaLocation; +int DefaultMetavoxelRendererImplementation::_splatTextureValueMaximaLocation; ProgramObject DefaultMetavoxelRendererImplementation::_lightHeightfieldProgram; int DefaultMetavoxelRendererImplementation::_lightHeightScaleLocation; ProgramObject DefaultMetavoxelRendererImplementation::_shadowLightHeightfieldProgram; diff --git a/interface/src/MetavoxelSystem.h b/interface/src/MetavoxelSystem.h index dc2ff8633a..752a060b86 100644 --- a/interface/src/MetavoxelSystem.h +++ b/interface/src/MetavoxelSystem.h @@ -249,6 +249,8 @@ public: static ProgramObject& getSplatHeightfieldProgram() { return _splatHeightfieldProgram; } static int getSplatHeightScaleLocation() { return _splatHeightScaleLocation; } static int getSplatTextureScaleLocation() { return _splatTextureScaleLocation; } + static int getSplatTextureValueMinimaLocation() { return _splatTextureValueMinimaLocation; } + static int getSplatTextureValueMaximaLocation() { return _splatTextureValueMaximaLocation; } static ProgramObject& getLightHeightfieldProgram() { return _lightHeightfieldProgram; } static int getLightHeightScaleLocation() { return _lightHeightScaleLocation; } @@ -292,6 +294,8 @@ private: static ProgramObject _splatHeightfieldProgram; static int _splatHeightScaleLocation; static int _splatTextureScaleLocation; + static int _splatTextureValueMinimaLocation; + static int _splatTextureValueMaximaLocation; static ProgramObject _lightHeightfieldProgram; static int _lightHeightScaleLocation; diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index c09848c201..94f0abaee4 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -932,6 +932,29 @@ void HeightfieldColorData::set(const QImage& image) { memcpy(_contents.data(), image.constBits(), _contents.size()); } +const int TEXTURE_HEADER_SIZE = sizeof(qint32) * 4; + +static QByteArray encodeTexture(int offsetX, int offsetY, int width, int height, const QByteArray& contents) { + QByteArray inflated(TEXTURE_HEADER_SIZE, 0); + qint32* header = (qint32*)inflated.data(); + *header++ = offsetX; + *header++ = offsetY; + *header++ = width; + *header++ = height; + inflated.append(contents); + return qCompress(inflated); +} + +static QByteArray decodeTexture(const QByteArray& encoded, int& offsetX, int& offsetY, int& width, int& height) { + QByteArray inflated = qUncompress(encoded); + const qint32* header = (const qint32*)inflated.constData(); + offsetX = *header++; + offsetY = *header++; + width = *header++; + height = *header++; + return inflated.mid(TEXTURE_HEADER_SIZE); +} + HeightfieldTextureData::HeightfieldTextureData(const QByteArray& contents, const QVector& textures) : HeightfieldData(contents), _textures(textures) { @@ -951,32 +974,31 @@ HeightfieldTextureData::HeightfieldTextureData(Bitstream& in, int bytes, const H in.readDelta(_textures, reference->getTextures()); reference->setDeltaData(HeightfieldDataPointer(this)); _contents = reference->getContents(); - QImage image = decodeHeightfieldImage(reference->getEncodedDelta()); - if (image.isNull()) { + + int offsetX, offsetY, width, height; + QByteArray delta = decodeTexture(reference->getEncodedDelta(), offsetX, offsetY, width, height); + if (delta.isEmpty()) { return; } - QPoint offset = image.offset(); - if (offset.x() == 0) { - set(image); + if (offsetX == 0) { + _contents = delta; return; } - int minX = offset.x() - 1; - int minY = offset.y() - 1; + int minX = offsetX - 1; + int minY = offsetY - 1; int size = glm::sqrt((float)_contents.size()); + const char* src = delta.constData(); char* dest = _contents.data() + minY * size + minX; - for (int y = 0; y < image.height(); y++) { - memcpy(dest, image.constScanLine(y), image.width()); - dest += size; + for (int y = 0; y < height; y++, src += width, dest += size) { + memcpy(dest, src, width); } } void HeightfieldTextureData::write(Bitstream& out) { QMutexLocker locker(&_encodedMutex); if (_encoded.isEmpty()) { - QImage image; int size = glm::sqrt((float)_contents.size()); - image = QImage((uchar*)_contents.data(), size, size, QImage::Format_Indexed8); - _encoded = encodeHeightfieldImage(image, true); + _encoded = encodeTexture(0, 0, size, size, _contents); } out << _encoded.size(); out.writeAligned(_encoded); @@ -990,7 +1012,6 @@ void HeightfieldTextureData::writeDelta(Bitstream& out, const HeightfieldTexture } QMutexLocker locker(&reference->getEncodedDeltaMutex()); if (reference->getEncodedDelta().isEmpty() || reference->getDeltaData() != this) { - QImage image; int size = glm::sqrt((float)_contents.size()); int minX = size, minY = size; int maxX = -1, maxY = -1; @@ -1010,18 +1031,19 @@ void HeightfieldTextureData::writeDelta(Bitstream& out, const HeightfieldTexture maxY = qMax(maxY, y); } } + QByteArray delta; + int width = 0, height = 0; if (maxX >= minX) { - int width = maxX - minX + 1; - int height = maxY - minY + 1; - image = QImage(width, height, QImage::Format_Indexed8); + width = maxX - minX + 1; + height = maxY - minY + 1; + delta = QByteArray(width * height, 0); + char* dest = delta.data(); src = _contents.constData() + minY * size + minX; - for (int y = 0; y < height; y++) { - memcpy(image.scanLine(y), src, width); - src += size; + for (int y = 0; y < height; y++, src += size, dest += width) { + memcpy(dest, src, width); } } - image.setOffset(QPoint(minX + 1, minY + 1)); - reference->setEncodedDelta(encodeHeightfieldImage(image, true)); + reference->setEncodedDelta(encodeTexture(minX + 1, minY + 1, width, height, delta)); reference->setDeltaData(HeightfieldDataPointer(this)); } out << reference->getEncodedDelta().size(); @@ -1030,15 +1052,11 @@ void HeightfieldTextureData::writeDelta(Bitstream& out, const HeightfieldTexture } void HeightfieldTextureData::read(Bitstream& in, int bytes) { - set(decodeHeightfieldImage(_encoded = in.readAligned(bytes))); + int offsetX, offsetY, width, height; + _contents = decodeTexture(_encoded = in.readAligned(bytes), offsetX, offsetY, width, height); in >> _textures; } -void HeightfieldTextureData::set(const QImage& image) { - _contents.resize(image.width() * image.height()); - memcpy(_contents.data(), image.constBits(), _contents.size()); -} - HeightfieldTexture::HeightfieldTexture() { } diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h index f3e6dc0b97..fb6ff5097d 100644 --- a/libraries/metavoxels/src/AttributeRegistry.h +++ b/libraries/metavoxels/src/AttributeRegistry.h @@ -540,7 +540,6 @@ public: private: void read(Bitstream& in, int bytes); - void set(const QImage& image); QVector _textures; };