khanat-opennel-code/code/nel/src/3d/water_env_map.cpp

396 lines
14 KiB
C++

// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2010 Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "std3d.h"
#include "nel/3d/water_env_map.h"
#include "nel/3d/vertex_buffer.h"
#include "nel/3d/index_buffer.h"
#include "nel/3d/material.h"
#include "nel/3d/driver.h"
#include "nel/3d/u_water_env_map.h"
#include "nel/misc/common.h"
#include "nel/3d/viewport.h"
namespace NL3D
{
CVertexBuffer CWaterEnvMap::_FlattenVB; // vb to map cube map top hemisphere to a 2D map
CIndexBuffer CWaterEnvMap::_FlattenIB("CWaterEnvMap::_FlattenIB");
bool CWaterEnvMap::_FlattenVBInitialized;
CMaterial CWaterEnvMap::_MaterialPassThru;
CMaterial CWaterEnvMap::_MaterialPassThruZTest;
CVertexBuffer CWaterEnvMap::_TestVB;
CIndexBuffer CWaterEnvMap::_TestIB("CWaterEnvMap::_TestIB");
// flatten vb params
static const uint FVB_NUM_SIDES = 32;
static const uint FVB_NUM_SECTIONS = 10;
static const uint FVB_NUM_VERTS = FVB_NUM_SIDES * (FVB_NUM_SECTIONS + 1);
static const uint FVB_NUM_TRIS = 2 * FVB_NUM_SIDES * FVB_NUM_SECTIONS;
// tmp : test vertex buffer
static const uint TEST_VB_NUM_SEGMENT = 16;
static const uint TEST_VB_NUM_SLICE = 16;
static const uint TEST_VB_NUM_TRIS = 2 * TEST_VB_NUM_SEGMENT * TEST_VB_NUM_SLICE;
// Get index of a vertex in the flatten vb from its side an section
static uint32 inline getFVBVertex(uint section, uint side)
{
nlassert(section <= FVB_NUM_SECTIONS);
return (uint32) (section + (side % FVB_NUM_SIDES) * (FVB_NUM_SECTIONS + 1));
}
const uint NUM_FACES_TO_RENDER = 5;
// *******************************************************************************
CWaterEnvMap::CWaterEnvMap()
{
_UpdateTime = 0;
_LastRenderTick = 0;
invalidate();
_NumRenderedFaces = 0;
_EnvCubicSize = 0;
_Env2DSize = 0;
_LastRenderTime = -1;
_StartRenderTime = -1;
_Alpha = 255;
}
// *******************************************************************************
void CWaterEnvMap::init(uint cubeMapSize, uint projection2DSize, TGlobalAnimationTime updateTime, IDriver &driver)
{
// Allocate cube map
// a cubic texture with no sharing allowed
class CTextureCubeUnshared : public CTextureCube
{
public:
virtual bool supportSharing() const {return false;}
virtual uint32 getWidth(uint32 numMipMap = 0) const
{
nlassert(numMipMap == 0);
return Size;
}
virtual uint32 getHeight(uint32 numMipMap = 0) const
{
nlassert(numMipMap == 0);
return Size;
}
uint32 Size;
};
// a 2D testure
class CTexture2DUnshared : public CTextureBlank
{
public:
virtual bool supportSharing() const {return false;}
virtual uint32 getWidth(uint32 numMipMap = 0) const
{
nlassert(numMipMap == 0);
return Size;
}
virtual uint32 getHeight(uint32 numMipMap = 0) const
{
nlassert(numMipMap == 0);
return Size;
}
uint32 Size;
};
nlassert(cubeMapSize > 0);
nlassert(NLMISC::isPowerOf2(cubeMapSize));
nlassert(projection2DSize > 0);
nlassert(NLMISC::isPowerOf2(projection2DSize));
CTextureCubeUnshared *envCubic = new CTextureCubeUnshared;
_EnvCubic = envCubic;
_EnvCubic->setRenderTarget(true); // we will render to the texture
_EnvCubic->setWrapS(ITexture::Clamp);
_EnvCubic->setWrapT(ITexture::Clamp);
_EnvCubic->setFilterMode(ITexture::Linear, ITexture::LinearMipMapOff);
CTexture2DUnshared *tb = new CTexture2DUnshared;
tb->resize(cubeMapSize, cubeMapSize); // Unfortunately, must allocate memory in order for the driver to figure out the size
// that it needs to allocate for the texture, though its datas are never used (it is a render target)
tb->Size = cubeMapSize;
tb->setFilterMode(ITexture::Linear, ITexture::LinearMipMapOff);
for(uint k = 0; k < 6; ++k)
{
_EnvCubic->setTexture((CTextureCube::TFace) k, tb);
_EnvCubic->getTexture((CTextureCube::TFace) k)->setRenderTarget(true);
}
envCubic->Size = cubeMapSize;
// setup the texture to force the driver to allocate vram for it
driver.setupTexture(*_EnvCubic);
tb->reset();
// Allocate projection 2D map
CTexture2DUnshared *env2D = new CTexture2DUnshared;
_Env2D = env2D;
_Env2D->resize(projection2DSize, projection2DSize);
env2D->Size = projection2DSize;
_Env2D->setWrapS(ITexture::Clamp);
_Env2D->setWrapT(ITexture::Clamp);
_Env2D->setRenderTarget(true); // we will render to the texture
_Env2D->setFilterMode(ITexture::Linear, ITexture::LinearMipMapOff);
driver.setupTexture(*_Env2D); // allocate vram
_Env2D->reset();
_UpdateTime = updateTime;
_LastRenderTime = -1;
invalidate();
_NumRenderedFaces = 0;
_EnvCubicSize = cubeMapSize;
_Env2DSize = projection2DSize;
}
// *******************************************************************************
void CWaterEnvMap::update(TGlobalAnimationTime time, IDriver &driver)
{
if (_LastRenderTime == time) return;
_LastRenderTime = time;
// First five updates are used to render the cubemap faces (bottom face is not rendered)
// Sixth update project the cubemap into a 2D texture
uint numTexToRender;
if (_UpdateTime > 0)
{
uint64 currRenderTick = (uint64) (time / (_UpdateTime / (NUM_FACES_TO_RENDER + 1)));
numTexToRender = (uint) (currRenderTick - _LastRenderTick);
_LastRenderTick = currRenderTick;
}
else
{
numTexToRender = NUM_FACES_TO_RENDER + 1;
}
if (!numTexToRender) return;
if (_NumRenderedFaces == 0)
{
_StartRenderTime = time;
}
uint lastCubeFacesToRender = std::min((uint) NUM_FACES_TO_RENDER, _NumRenderedFaces + numTexToRender); // we don't render negative Z (only top hemisphere is used)
for(uint k = _NumRenderedFaces; k < lastCubeFacesToRender; ++k)
{
driver.setRenderTarget(_EnvCubic, 0, 0, _EnvCubicSize, _EnvCubicSize, 0, (uint32) k);
render((CTextureCube::TFace) k, _StartRenderTime);
}
_NumRenderedFaces = lastCubeFacesToRender;
if (_NumRenderedFaces == NUM_FACES_TO_RENDER && (_NumRenderedFaces + numTexToRender) > NUM_FACES_TO_RENDER)
{
// render to 2D map
driver.setRenderTarget(_Env2D, 0, 0, _Env2DSize, _Env2DSize);
doInit();
//
driver.activeVertexProgram(NULL);
driver.activeVertexBuffer(_FlattenVB);
driver.activeIndexBuffer(_FlattenIB);
driver.setFrustum(-1.f, 1.f, -1.f, 1.f, 0.f, 1.f, false);
driver.setupViewMatrix(CMatrix::Identity);
CMatrix mat;
//mat.scale(0.8f);
driver.setupModelMatrix(mat);
_MaterialPassThru.setTexture(0, _EnvCubic);
_MaterialPassThru.texConstantColor(0, CRGBA(255, 255, 255, _Alpha));
driver.renderTriangles(_MaterialPassThru, 0, FVB_NUM_TRIS);
_NumRenderedFaces = 0; // start to render again
}
driver.setRenderTarget(NULL);
}
// *******************************************************************************
void CWaterEnvMap::doInit()
{
if (!_FlattenVBInitialized)
{
initFlattenVB();
initTestVB();
_FlattenVBInitialized = true;
_MaterialPassThru.setLighting(false);
_MaterialPassThru.texEnvOpRGB(0, CMaterial::Replace);
_MaterialPassThru.texEnvArg0RGB(0, CMaterial::Texture, CMaterial::SrcColor);
_MaterialPassThru.texEnvOpAlpha(0, CMaterial::Replace);
_MaterialPassThru.texEnvArg0Alpha(0, CMaterial::Constant, CMaterial::SrcAlpha);
_MaterialPassThru.texConstantColor(0, CRGBA(255, 255, 255, 255));
_MaterialPassThru.setDoubleSided(true);
_MaterialPassThruZTest = _MaterialPassThru;
_MaterialPassThru.setZWrite(false);
_MaterialPassThru.setZFunc(CMaterial::always);
}
}
static CVertexProgram testMeshVP(
"!!VP1.0\n\
DP4 o[HPOS].x, c[0], v[0]; \n\
DP4 o[HPOS].y, c[1], v[0]; \n\
DP4 o[HPOS].z, c[2], v[0]; \n\
DP4 o[HPOS].w, c[3], v[0]; \n\
MAD o[COL0], v[8], c[4].xxxx, c[4].yyyy; \n\
MOV o[TEX0], v[8]; \n\
END"
);
// *******************************************************************************
void CWaterEnvMap::renderTestMesh(IDriver &driver)
{
doInit();
CMaterial testMat;
testMat.setLighting(false);
testMat.texEnvOpRGB(0, CMaterial::Modulate);
testMat.texEnvArg0RGB(0, CMaterial::Texture, CMaterial::SrcColor);
testMat.texEnvArg0RGB(1, CMaterial::Diffuse, CMaterial::SrcColor);
testMat.texEnvOpAlpha(0, CMaterial::Replace);
testMat.texEnvArg0Alpha(0, CMaterial::Constant, CMaterial::SrcAlpha);
testMat.texConstantColor(0, CRGBA(255, 255, 255, 255));
testMat.setDoubleSided(true);
testMat.setZWrite(false);
testMat.setZFunc(CMaterial::always);
// tmp : test cubemap
driver.activeVertexProgram(&testMeshVP);
driver.activeVertexBuffer(_TestVB);
driver.activeIndexBuffer(_TestIB);
driver.setConstantMatrix(0, IDriver::ModelViewProjection, IDriver::Identity); // tmp
_MaterialPassThruZTest.setTexture(0, _EnvCubic);
driver.setConstantMatrix(0, IDriver::ModelViewProjection, IDriver::Identity);
driver.setConstant(4, 2.f, 1.f, 0.f, 0.f);
//driver.renderTriangles(testMat, 0, TEST_VB_NUM_TRIS);
driver.renderTriangles(_MaterialPassThruZTest, 0, TEST_VB_NUM_TRIS);
driver.activeVertexProgram(NULL);
}
// *******************************************************************************
void CWaterEnvMap::initFlattenVB()
{
_FlattenVB.setPreferredMemory(CVertexBuffer::AGPPreferred, true);
_FlattenVB.setName("Flatten VB");
_FlattenVB.clearValueEx();
_FlattenVB.addValueEx (CVertexBuffer::Position, CVertexBuffer::Float3);
_FlattenVB.addValueEx (CVertexBuffer::TexCoord0, CVertexBuffer::Float3);
_FlattenVB.initEx();
nlctassert(FVB_NUM_SIDES % 4 == 0); // number of sides must be a multiple of 4 so that sections sides will align with corners
_FlattenVB.setNumVertices(FVB_NUM_VERTS);
_FlattenIB.setFormat(NL_DEFAULT_INDEX_BUFFER_FORMAT);
_FlattenIB.setNumIndexes(3 * FVB_NUM_TRIS);
{
CVertexBufferReadWrite vbrw;
CIndexBufferReadWrite ibrw;
_FlattenVB.lock(vbrw);
_FlattenIB.lock(ibrw);
for(uint l = 0; l < FVB_NUM_SIDES; ++l)
{
double angle = NLMISC::Pi * 0.25 + 2 * NLMISC::Pi * (double) l / (double) FVB_NUM_SIDES;
for(uint k = 0; k < FVB_NUM_SECTIONS + 1; ++k)
{
double radius = (double) k / (double) (FVB_NUM_SECTIONS - 1);
float x = (float) (radius * cos(angle));
float y = (float) (radius * sin(angle));
if (k < FVB_NUM_SECTIONS)
{
ibrw.setTri(3 * 2 * (k + (l * FVB_NUM_SECTIONS)), getFVBVertex(k, l), getFVBVertex(k + 1, l + 1), getFVBVertex(k + 1, l));
ibrw.setTri(3 * (2 * (k + (l * FVB_NUM_SECTIONS)) + 1), getFVBVertex(k, l), getFVBVertex(k, l + 1), getFVBVertex(k + 1, l + 1));
}
else
{
uint side = l / (FVB_NUM_SIDES / 4);
switch(side)
{
case 0: // top
x /= y;
y = 1.f;
break;
case 1: // left
y /= -x;
x = -1.f;
break;
case 2: // bottom
x /= -y;
y = -1.f;
break;
case 3: // right
y /= x;
x = 1.f;
break;
default:
nlassert(0);
break;
}
}
CVector dir;
//dir.sphericToCartesian(1.f, (float) angle, (float) (NLMISC::Pi * 0.5 * acos(std::max(0.f, (1.f - (float) k / (FVB_NUM_SECTIONS - 1))))));
dir.sphericToCartesian(1.f, (float) angle, (float) acos(std::min(1.f, (float) k / (FVB_NUM_SECTIONS - 1))));
vbrw.setValueFloat3Ex(CVertexBuffer::Position, getFVBVertex(k, l), x, 0.5f, y);
vbrw.setValueFloat3Ex(CVertexBuffer::TexCoord0, getFVBVertex(k, l), -dir.x, dir.z, -dir.y);
}
}
}
}
// *******************************************************************************
void CWaterEnvMap::invalidate()
{
_LastRenderTime = -1;
_StartRenderTime = -1;
if (_UpdateTime == 0)
{
_LastRenderTick = std::numeric_limits<uint64>::max();
}
else
{
_LastRenderTick -= (NUM_FACES_TO_RENDER + 1);
}
_NumRenderedFaces = 0;
}
// *******************************************************************************
void CWaterEnvMap::initTestVB()
{
_TestVB.setPreferredMemory(CVertexBuffer::AGPPreferred, true);
_TestVB.setName("TestVB");
_TestVB.clearValueEx();
_TestVB.addValueEx (CVertexBuffer::Position, CVertexBuffer::Float3);
_TestVB.addValueEx (CVertexBuffer::TexCoord0, CVertexBuffer::Float3);
_TestVB.initEx();
_TestVB.setNumVertices(TEST_VB_NUM_SEGMENT * 2 * (TEST_VB_NUM_SLICE + 1));
_TestIB.setFormat(NL_DEFAULT_INDEX_BUFFER_FORMAT);
_TestIB.setNumIndexes(3 * TEST_VB_NUM_TRIS);
{
CVertexBufferReadWrite vbrw;
CIndexBufferReadWrite ibrw;
_TestVB.lock(vbrw);
_TestIB.lock(ibrw);
uint triIndex = 0;
for(uint k = 0; k < TEST_VB_NUM_SEGMENT; ++k)
{
float theta = 2 * (float) (NLMISC::Pi * (double) k / (double) TEST_VB_NUM_SEGMENT);
for(uint l = 0; l <= TEST_VB_NUM_SLICE; ++l)
{
float phi = (float) (NLMISC::Pi / 2 * (1 - 2 * (double) l / (double) TEST_VB_NUM_SLICE));
CVector pos;
pos.sphericToCartesian(1.f, theta, phi);
#define VERT_INDEX(k, l) ((l) + ((k) % TEST_VB_NUM_SEGMENT) * (TEST_VB_NUM_SLICE + 1))
vbrw.setVertexCoord(VERT_INDEX(k, l), pos);
vbrw.setValueFloat3Ex(CVertexBuffer::TexCoord0, VERT_INDEX(k, l), pos);
if (l != TEST_VB_NUM_SLICE)
{
ibrw.setTri(3 * triIndex++, VERT_INDEX(k, l), VERT_INDEX(k + 1, l), VERT_INDEX(k + 1, l + 1));
ibrw.setTri(3 * triIndex++, VERT_INDEX(k, l), VERT_INDEX(k + 1, l + 1), VERT_INDEX(k, l + 1));
}
}
}
nlassert(triIndex == TEST_VB_NUM_TRIS);
}
}
} // NL3D