openspades/Sources/Draw/GLBasicShadowMapRenderer.cpp
2013-09-11 13:50:18 +09:00

341 lines
9.7 KiB
C++

/*
Copyright (c) 2013 yvt
This file is part of OpenSpades.
OpenSpades is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
OpenSpades 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with OpenSpades. If not, see <http://www.gnu.org/licenses/>.
*/
#include "GLBasicShadowMapRenderer.h"
#include "IGLDevice.h"
#include "GLRenderer.h"
#include "../Core/Debug.h"
#include "../Core/Exception.h"
#include "../Core/Settings.h"
#include "GLProfiler.h"
SPADES_SETTING(r_shadowMapSize, "2048");
namespace spades {
namespace draw {
GLBasicShadowMapRenderer::GLBasicShadowMapRenderer(GLRenderer *r):
IGLShadowMapRenderer(r){
SPADES_MARK_FUNCTION();
device = r->GetGLDevice();
textureSize = r_shadowMapSize;
colorTexture = device->GenTexture();
device->BindTexture(IGLDevice::Texture2D,
colorTexture);
device->TexImage2D(IGLDevice::Texture2D,
0,
IGLDevice::RGB,
textureSize,
textureSize,
0,
IGLDevice::RGB,
IGLDevice::UnsignedByte, NULL);
device->TexParamater(IGLDevice::Texture2D,
IGLDevice::TextureMagFilter,
IGLDevice::Linear);
device->TexParamater(IGLDevice::Texture2D,
IGLDevice::TextureMinFilter,
IGLDevice::Linear);
device->TexParamater(IGLDevice::Texture2D,
IGLDevice::TextureWrapS,
IGLDevice::ClampToEdge);
device->TexParamater(IGLDevice::Texture2D,
IGLDevice::TextureWrapT,
IGLDevice::ClampToEdge);
for(int i = 0; i < NumSlices; i++){
texture[i] = device->GenTexture();
device->BindTexture(IGLDevice::Texture2D, texture[i]);
device->TexImage2D(IGLDevice::Texture2D,
0,
IGLDevice::DepthComponent24,
textureSize,
textureSize,
0,
IGLDevice::DepthComponent,
IGLDevice::UnsignedInt, NULL);
device->TexParamater(IGLDevice::Texture2D,
IGLDevice::TextureMagFilter,
IGLDevice::Linear);
device->TexParamater(IGLDevice::Texture2D,
IGLDevice::TextureMinFilter,
IGLDevice::Linear);
device->TexParamater(IGLDevice::Texture2D,
IGLDevice::TextureWrapS,
IGLDevice::ClampToEdge);
device->TexParamater(IGLDevice::Texture2D,
IGLDevice::TextureWrapT,
IGLDevice::ClampToEdge);
framebuffer[i] = device->GenFramebuffer();
device->BindFramebuffer(IGLDevice::Framebuffer,
framebuffer[i]);
device->FramebufferTexture2D(IGLDevice::Framebuffer,
IGLDevice::ColorAttachment0,
IGLDevice::Texture2D,
colorTexture, 0);
device->FramebufferTexture2D(IGLDevice::Framebuffer,
IGLDevice::DepthAttachment,
IGLDevice::Texture2D,
texture[i], 0);
device->BindFramebuffer(IGLDevice::Framebuffer,
0);
}
}
GLBasicShadowMapRenderer::~GLBasicShadowMapRenderer(){
SPADES_MARK_FUNCTION();
for(int i = 0; i < NumSlices; i++){
device->DeleteTexture(texture[i]);
device->DeleteFramebuffer(framebuffer[i]);
}
device->DeleteTexture(colorTexture);
}
#define Segment GLBSMRSegment
struct Segment {
float low, high;
bool empty;
Segment():empty(true){}
Segment(float l, float h):
low(std::min(l,h)), high(std::max(l,h)),empty(false){}
void operator +=(const Segment& seg) {
if(seg.empty)
return;
if(empty){
low = seg.low;
high = seg.high;
empty = false;
}else{
low = std::min(low, seg.low);
high = std::max(high, seg.high);
}
}
void operator +=(float v){
if(empty){
low = high = v;
}else{
if(v < low)low = v;
if(v > high) high = v;
}
}
};
static Vector3 FrustrumCoord(const client::SceneDefinition& def,
float x, float y, float z){
x *= z; y *= z;
return def.viewOrigin +
def.viewAxis[0] * x +
def.viewAxis[1] * y +
def.viewAxis[2] * z;
}
static Segment ZRange(Vector3 base, Vector3 dir,
Plane3 plane1, Plane3 plane2){
return Segment(plane1.GetDistanceTo(base)/Vector3::Dot(dir,plane1.n),
plane2.GetDistanceTo(base)/Vector3::Dot(dir,plane2.n));
}
void GLBasicShadowMapRenderer::BuildMatrix(float near, float far){
// TODO: variable light direction?
Vector3 lightDir = MakeVector3(0, -1, -1).Normalize();
// set better up dir?
Vector3 up = MakeVector3(0, 0, 1);
Vector3 side = Vector3::Cross(up, lightDir).Normalize();
up = Vector3::Cross(lightDir, side).Normalize();
// build frustrum
client::SceneDefinition def = GetRenderer()->GetSceneDef();
Vector3 frustrum[8];
float tanX = tanf(def.fovX * .5f);
float tanY = tanf(def.fovY * .5f);
frustrum[0] = FrustrumCoord(def, tanX, tanY, near);
frustrum[1] = FrustrumCoord(def, tanX, -tanY, near);
frustrum[2] = FrustrumCoord(def, -tanX, tanY, near);
frustrum[3] = FrustrumCoord(def, -tanX, -tanY, near);
frustrum[4] = FrustrumCoord(def, tanX, tanY, far);
frustrum[5] = FrustrumCoord(def, tanX, -tanY, far);
frustrum[6] = FrustrumCoord(def, -tanX, tanY, far);
frustrum[7] = FrustrumCoord(def, -tanX, -tanY, far);
// compute frustrum's x,y boundary
float minX, maxX, minY, maxY;
minX = maxX = Vector3::Dot(frustrum[0], side);
minY = maxY = Vector3::Dot(frustrum[0], up);
for(int i = 1; i < 8; i++){
float x = Vector3::Dot(frustrum[i], side);
float y = Vector3::Dot(frustrum[i], up);
if(x < minX) minX = x;
if(x > maxX) maxX = x;
if(y < minY) minY = y;
if(y > maxY) maxY = y;
}
// compute frustrum's z boundary
Segment seg;
Plane3 plane1(0,0,1,-4.f);
Plane3 plane2(0,0,1,64.f);
seg += ZRange(side * minX + up * minY,
lightDir, plane1, plane2);
seg += ZRange(side * minX + up * maxY,
lightDir, plane1, plane2);
seg += ZRange(side * maxX + up * minY,
lightDir, plane1, plane2);
seg += ZRange(side * maxX + up * maxY,
lightDir, plane1, plane2);
for(int i = 1; i < 8; i++){
seg += Vector3::Dot(frustrum[i], lightDir);
}
// build frustrum obb
Vector3 origin = side * minX + up * minY + lightDir * seg.low;
Vector3 axis1 = side * (maxX - minX);
Vector3 axis2 = up * (maxY - minY);
Vector3 axis3 = lightDir * (seg.high - seg.low);
obb = OBB3(Matrix4::FromAxis(axis1, axis2, axis3,
origin));
vpWidth = 2.f / axis1.GetLength();
vpHeight = 2.f / axis2.GetLength();
// convert to projectionview matrix
matrix = obb.m.InversedFast();
matrix = Matrix4::Scale(2.f) * matrix;
matrix = Matrix4::Translate(-1, -1, -1) * matrix;
// scale a little big for padding
matrix = Matrix4::Scale(.98f) * matrix;
//
matrix = Matrix4::Scale(1,1,-1) * matrix;
// make sure frustrums in range
#ifndef NDEBUG
for(int i = 0; i < 8; i++){
Vector4 v = matrix * frustrum[i];
SPAssert(v.x >= -1.f);
SPAssert(v.y >= -1.f);
//SPAssert(v.z >= -1.f);
SPAssert(v.x < 1.f);
SPAssert(v.y < 1.f);
//SPAssert(v.z < 1.f);
}
#endif
}
void GLBasicShadowMapRenderer::Render() {
SPADES_MARK_FUNCTION();
IGLDevice::Integer lastFb = device->GetInteger(IGLDevice::FramebufferBinding);
//client::SceneDefinition def = GetRenderer()->GetSceneDef();
float nearDist = 0.f;
for(int i = 0; i < NumSlices; i++){
GLProfiler profiler(device, "Slice %d / %d", i + 1,
(int)NumSlices);
float farDist;
// TODO: variable far distance according to the scene definition
// (note that this needs uniform shader variable)
switch(i){
case 0:
farDist = 12.f;
break;
case 1:
farDist = 40.f;
break;
case 2:
farDist = 150.f;
break;
}
BuildMatrix(nearDist, farDist);
matrices[i] = matrix;
/*
printf("m[%d]=\n[%f,%f,%f,%f]\n[%f,%f,%f,%f]\n[%f,%f,%f,%f]\n[%f,%f,%f,%f]\n",
i, matrix.m[0], matrix.m[4], matrix.m[8], matrix.m[12],
matrix.m[1], matrix.m[5], matrix.m[9], matrix.m[13],
matrix.m[2], matrix.m[6], matrix.m[10], matrix.m[14],
matrix.m[3], matrix.m[7], matrix.m[11], matrix.m[15]);*/
/*
matrix = Matrix4::Identity();
matrix = Matrix4::Scale(1.f / 16.f);
matrix = matrix * Matrix4::Rotate(MakeVector3(1, 0, 0), M_PI / 4.f);
matrix = matrix * Matrix4::Translate(-def.viewOrigin);
matrix = Matrix4::Scale(1,1,16.f / 70.f) * matrix;*/
device->BindFramebuffer(IGLDevice::Framebuffer,
framebuffer[i]);
device->Viewport(0, 0, textureSize, textureSize);
device->ClearDepth(1.f);
device->Clear(IGLDevice::DepthBufferBit);
RenderShadowMapPass();
nearDist = farDist;
}
device->BindFramebuffer(IGLDevice::Framebuffer,
lastFb);
device->Viewport(0, 0, device->ScreenWidth(), device->ScreenHeight());
}
bool GLBasicShadowMapRenderer::Cull(const spades::AABB3 &){
// TODO: who uses this?
SPNotImplemented();
return true;
}
bool GLBasicShadowMapRenderer::SphereCull(const spades::Vector3 &center,
float rad){
Vector4 vw = matrix * center;
float xx = fabsf(vw.x);
float yy = fabsf(vw.y);
float rx = rad * vpWidth;
float ry = rad * vpHeight;
return xx < (1.f + rx) && yy < (1.f + ry);
//return true;
//return obb.GetDistanceTo(center) < rad;
}
}
}