/* 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 . */ #include "GLVoxelModel.h" #include #include "GLDynamicLightShader.h" #include "GLImage.h" #include "GLProgram.h" #include "GLProgramAttribute.h" #include "GLProgramUniform.h" #include "GLRenderer.h" #include "GLShadowMapShader.h" #include "GLShadowShader.h" #include "IGLShadowMapRenderer.h" namespace spades { namespace draw { void GLVoxelModel::PreloadShaders(spades::draw::GLRenderer *renderer) { renderer->RegisterProgram("Shaders/VoxelModel.program"); renderer->RegisterProgram("Shaders/VoxelModelDynamicLit.program"); renderer->RegisterProgram("Shaders/VoxelModelShadowMap.program"); renderer->RegisterImage("Gfx/AmbientOcclusion.png"); } GLVoxelModel::GLVoxelModel(VoxelModel *m, GLRenderer *r) { SPADES_MARK_FUNCTION(); renderer = r; device = r->GetGLDevice(); program = renderer->RegisterProgram("Shaders/VoxelModel.program"); dlightProgram = renderer->RegisterProgram("Shaders/VoxelModelDynamicLit.program"); shadowMapProgram = renderer->RegisterProgram("Shaders/VoxelModelShadowMap.program"); aoImage = (GLImage *)renderer->RegisterImage("Gfx/AmbientOcclusion.png"); BuildVertices(m); buffer = device->GenBuffer(); device->BindBuffer(IGLDevice::ArrayBuffer, buffer); device->BufferData(IGLDevice::ArrayBuffer, static_cast(vertices.size() * sizeof(Vertex)), vertices.data(), IGLDevice::StaticDraw); idxBuffer = device->GenBuffer(); device->BindBuffer(IGLDevice::ArrayBuffer, idxBuffer); device->BufferData(IGLDevice::ArrayBuffer, static_cast(indices.size() * sizeof(uint32_t)), indices.data(), IGLDevice::StaticDraw); device->BindBuffer(IGLDevice::ArrayBuffer, 0); origin = m->GetOrigin(); origin -= .5f; // (0,0,0) is center of voxel (0,0,0) Vector3 minPos = {0, 0, 0}; Vector3 maxPos = {(float)m->GetWidth(), (float)m->GetHeight(), (float)m->GetDepth()}; minPos += origin; maxPos += origin; Vector3 maxDiff = {std::max(fabsf(minPos.x), fabsf(maxPos.x)), std::max(fabsf(minPos.y), fabsf(maxPos.y)), std::max(fabsf(minPos.z), fabsf(maxPos.z))}; radius = maxDiff.GetLength(); boundingBox.min = minPos; boundingBox.max = maxPos; // clean up numIndices = (unsigned int)indices.size(); std::vector().swap(vertices); std::vector().swap(indices); } GLVoxelModel::~GLVoxelModel() { SPADES_MARK_FUNCTION(); device->DeleteBuffer(idxBuffer); device->DeleteBuffer(buffer); } uint8_t GLVoxelModel::calcAOID(VoxelModel *m, int x, int y, int z, int ux, int uy, int uz, int vx, int vy, int vz) { int v = 0; if (m->IsSolid(x - ux, y - uy, z - uz)) v |= 1; if (m->IsSolid(x + ux, y + uy, z + uz)) v |= 1 << 1; if (m->IsSolid(x - vx, y - vy, z - vz)) v |= 1 << 2; if (m->IsSolid(x + vx, y + vy, z + vz)) v |= 1 << 3; if (m->IsSolid(x - ux + vx, y - uy + vy, z - uz + vz)) v |= 1 << 4; if (m->IsSolid(x - ux - vx, y - uy - vy, z - uz - vz)) v |= 1 << 5; if (m->IsSolid(x + ux + vx, y + uy + vy, z + uz + vz)) v |= 1 << 6; if (m->IsSolid(x + ux - vx, y + uy - vy, z + uz - vz)) v |= 1 << 7; return (uint8_t)v; } void GLVoxelModel::EmitFace(spades::VoxelModel *model, int x, int y, int z, int nx, int ny, int nz, uint32_t color) { SPADES_MARK_FUNCTION_DEBUG(); // decide face tangent int ux = ny, uy = nz, uz = nx; int vx = nz, vy = nx, vz = ny; if (nx < 0 || ny < 0 || nz < 0) { vx = -vx; vy = -vy; vz = -vz; } // now cross(u, v) = n (somehow) SPAssert(ux * vx + uy * vy + uz * vz == 0); SPAssert(ux * nx + uy * ny + uz * nz == 0); SPAssert(nx * vx + ny * vy + nz * vz == 0); SPAssert(uy * vz - uz * vy == -nx); SPAssert(uz * vx - ux * vz == -ny); SPAssert(ux * vy - uy * vx == -nz); // decide face origin int fx = 0, fy = 0, fz = 0; if (ux == -1 || vx == -1) { fx = 1; SPAssert(ux + vx == -1); } if (uy == -1 || vy == -1) { fy = 1; SPAssert(uy + vy == -1); } if (uz == -1 || vz == -1) { fz = 1; SPAssert(uz + vz == -1); } SPAssert(nx * fx + ny * fy + nz * fz == 0); uint8_t aoID = calcAOID(model, x + nx, y + ny, z + nz, ux, uy, uz, vx, vy, vz); Vertex v; uint32_t idx = (uint32_t)vertices.size(); v.aoID = aoID; v.red = (uint8_t)(color); v.green = (uint8_t)(color >> 8); v.blue = (uint8_t)(color >> 16); v.diffuse = 255; v.nx = nx; v.ny = ny; v.nz = nz; x += fx; y += fy; z += fz; if (nx > 0) x++; if (ny > 0) y++; if (nz > 0) z++; SPAssert(x >= 0); SPAssert(y >= 0); SPAssert(y >= 0); SPAssert(x + ux >= 0); SPAssert(y + uy >= 0); SPAssert(z + uz >= 0); SPAssert(x + vx >= 0); SPAssert(y + vy >= 0); SPAssert(z + vz >= 0); SPAssert(x + ux + vx >= 0); SPAssert(y + uy + vy >= 0); SPAssert(z + uz + vz >= 0); v.u = 0; v.v = 0; v.x = x; v.y = y; v.z = z; vertices.push_back(v); v.u = 1; v.v = 0; v.x = x + ux; v.y = y + uy; v.z = z + uz; vertices.push_back(v); v.u = 0; v.v = 1; v.x = x + vx; v.y = y + vy; v.z = z + vz; vertices.push_back(v); v.u = 1; v.v = 1; v.x = x + ux + vx; v.y = y + uy + vy; v.z = z + uz + vz; vertices.push_back(v); indices.push_back(idx); indices.push_back(idx + 1); indices.push_back(idx + 2); indices.push_back(idx + 1); indices.push_back(idx + 3); indices.push_back(idx + 2); } void GLVoxelModel::BuildVertices(spades::VoxelModel *model) { SPADES_MARK_FUNCTION(); SPAssert(vertices.empty()); SPAssert(indices.empty()); int w = model->GetWidth(); int h = model->GetHeight(); int d = model->GetDepth(); for (int x = 0; x < w; x++) { for (int y = 0; y < h; y++) { for (int z = 0; z < d; z++) { if (!model->IsSolid(x, y, z)) continue; uint32_t color = model->GetColor(x, y, z); color |= 0xff000000UL; if (!model->IsSolid(x - 1, y, z)) EmitFace(model, x, y, z, -1, 0, 0, color); if (!model->IsSolid(x + 1, y, z)) EmitFace(model, x, y, z, 1, 0, 0, color); if (!model->IsSolid(x, y - 1, z)) EmitFace(model, x, y, z, 0, -1, 0, color); if (!model->IsSolid(x, y + 1, z)) EmitFace(model, x, y, z, 0, 1, 0, color); if (!model->IsSolid(x, y, z - 1)) EmitFace(model, x, y, z, 0, 0, -1, color); if (!model->IsSolid(x, y, z + 1)) EmitFace(model, x, y, z, 0, 0, 1, color); } } } } void GLVoxelModel::Prerender(std::vector params) { SPADES_MARK_FUNCTION(); RenderSunlightPass(params); } void GLVoxelModel::RenderShadowMapPass(std::vector params) { SPADES_MARK_FUNCTION(); device->Enable(IGLDevice::CullFace, true); device->Enable(IGLDevice::DepthTest, true); shadowMapProgram->Use(); static GLShadowMapShader shadowMapShader; shadowMapShader(renderer, shadowMapProgram, 0); static GLProgramUniform modelOrigin("modelOrigin"); modelOrigin(shadowMapProgram); modelOrigin.SetValue(origin.x, origin.y, origin.z); // setup attributes static GLProgramAttribute positionAttribute("positionAttribute"); static GLProgramAttribute normalAttribute("normalAttribute"); positionAttribute(shadowMapProgram); normalAttribute(shadowMapProgram); device->BindBuffer(IGLDevice::ArrayBuffer, buffer); device->VertexAttribPointer(positionAttribute(), 4, IGLDevice::UnsignedByte, false, sizeof(Vertex), (void *)0); if (normalAttribute() != -1) { device->VertexAttribPointer(normalAttribute(), 3, IGLDevice::Byte, false, sizeof(Vertex), (void *)12); } device->BindBuffer(IGLDevice::ArrayBuffer, 0); device->EnableVertexAttribArray(positionAttribute(), true); if (normalAttribute() != -1) device->EnableVertexAttribArray(normalAttribute(), true); device->BindBuffer(IGLDevice::ElementArrayBuffer, idxBuffer); for (size_t i = 0; i < params.size(); i++) { const client::ModelRenderParam ¶m = params[i]; // frustrum cull float rad = radius; rad *= param.matrix.GetAxis(0).GetLength(); if (param.depthHack) continue; if (!renderer->GetShadowMapRenderer()->SphereCull(param.matrix.GetOrigin(), rad)) { continue; } Matrix4 modelMatrix = param.matrix; static GLProgramUniform modelMatrixU("modelMatrix"); modelMatrixU(shadowMapProgram); modelMatrixU.SetValue(modelMatrix); modelMatrix.m[12] = 0.f; modelMatrix.m[13] = 0.f; modelMatrix.m[14] = 0.f; static GLProgramUniform modelNormalMatrix("modelNormalMatrix"); modelNormalMatrix(shadowMapProgram); modelNormalMatrix.SetValue(modelMatrix); device->DrawElements(IGLDevice::Triangles, numIndices, IGLDevice::UnsignedInt, (void *)0); } device->BindBuffer(IGLDevice::ElementArrayBuffer, 0); device->EnableVertexAttribArray(positionAttribute(), false); if (normalAttribute() != -1) device->EnableVertexAttribArray(normalAttribute(), false); device->ActiveTexture(0); device->BindTexture(IGLDevice::Texture2D, 0); } void GLVoxelModel::RenderSunlightPass(std::vector params) { SPADES_MARK_FUNCTION(); device->ActiveTexture(0); aoImage->Bind(IGLDevice::Texture2D); device->TexParamater(IGLDevice::Texture2D, IGLDevice::TextureMinFilter, IGLDevice::Linear); device->Enable(IGLDevice::CullFace, true); device->Enable(IGLDevice::DepthTest, true); program->Use(); static GLShadowShader shadowShader; shadowShader(renderer, program, 1); static GLProgramUniform fogDistance("fogDistance"); fogDistance(program); fogDistance.SetValue(renderer->GetFogDistance()); static GLProgramUniform fogColor("fogColor"); fogColor(program); Vector3 fogCol = renderer->GetFogColorForSolidPass(); fogCol *= fogCol; fogColor.SetValue(fogCol.x, fogCol.y, fogCol.z); static GLProgramUniform aoUniform("ambientOcclusionTexture"); aoUniform(program); aoUniform.SetValue(0); static GLProgramUniform modelOrigin("modelOrigin"); modelOrigin(program); modelOrigin.SetValue(origin.x, origin.y, origin.z); static GLProgramUniform sunLightDirection("sunLightDirection"); sunLightDirection(program); Vector3 sunPos = MakeVector3(0, -1, -1); sunPos = sunPos.Normalize(); sunLightDirection.SetValue(sunPos.x, sunPos.y, sunPos.z); static GLProgramUniform viewOriginVector("viewOriginVector"); viewOriginVector(program); const auto &viewOrigin = renderer->GetSceneDef().viewOrigin; viewOriginVector.SetValue(viewOrigin.x, viewOrigin.y, viewOrigin.z); // setup attributes static GLProgramAttribute positionAttribute("positionAttribute"); static GLProgramAttribute textureCoordAttribute("textureCoordAttribute"); static GLProgramAttribute colorAttribute("colorAttribute"); static GLProgramAttribute normalAttribute("normalAttribute"); positionAttribute(program); textureCoordAttribute(program); colorAttribute(program); normalAttribute(program); device->BindBuffer(IGLDevice::ArrayBuffer, buffer); device->VertexAttribPointer(positionAttribute(), 4, IGLDevice::UnsignedByte, false, sizeof(Vertex), (void *)0); device->VertexAttribPointer(textureCoordAttribute(), 2, IGLDevice::UnsignedShort, false, sizeof(Vertex), (void *)4); device->VertexAttribPointer(colorAttribute(), 4, IGLDevice::UnsignedByte, true, sizeof(Vertex), (void *)8); device->VertexAttribPointer(normalAttribute(), 3, IGLDevice::Byte, false, sizeof(Vertex), (void *)12); device->BindBuffer(IGLDevice::ArrayBuffer, 0); device->EnableVertexAttribArray(positionAttribute(), true); device->EnableVertexAttribArray(textureCoordAttribute(), true); device->EnableVertexAttribArray(colorAttribute(), true); device->EnableVertexAttribArray(normalAttribute(), true); device->BindBuffer(IGLDevice::ElementArrayBuffer, idxBuffer); for (size_t i = 0; i < params.size(); i++) { const client::ModelRenderParam ¶m = params[i]; // frustrum cull float rad = radius; rad *= param.matrix.GetAxis(0).GetLength(); if (!renderer->SphereFrustrumCull(param.matrix.GetOrigin(), rad)) { continue; } static GLProgramUniform customColor("customColor"); customColor(program); customColor.SetValue(param.customColor.x, param.customColor.y, param.customColor.z); Matrix4 modelMatrix = param.matrix; static GLProgramUniform projectionViewModelMatrix("projectionViewModelMatrix"); projectionViewModelMatrix(program); projectionViewModelMatrix.SetValue(renderer->GetProjectionViewMatrix() * modelMatrix); static GLProgramUniform viewModelMatrix("viewModelMatrix"); viewModelMatrix(program); viewModelMatrix.SetValue(renderer->GetViewMatrix() * modelMatrix); static GLProgramUniform modelMatrixU("modelMatrix"); modelMatrixU(program); modelMatrixU.SetValue(modelMatrix); modelMatrix.m[12] = 0.f; modelMatrix.m[13] = 0.f; modelMatrix.m[14] = 0.f; static GLProgramUniform modelNormalMatrix("modelNormalMatrix"); modelNormalMatrix(program); modelNormalMatrix.SetValue(modelMatrix); if (param.depthHack) { device->DepthRange(0.f, 0.1f); } device->DrawElements(IGLDevice::Triangles, numIndices, IGLDevice::UnsignedInt, (void *)0); if (param.depthHack) { device->DepthRange(0.f, 1.f); } } device->BindBuffer(IGLDevice::ElementArrayBuffer, 0); device->EnableVertexAttribArray(positionAttribute(), false); device->EnableVertexAttribArray(textureCoordAttribute(), false); device->EnableVertexAttribArray(colorAttribute(), false); device->EnableVertexAttribArray(normalAttribute(), false); device->ActiveTexture(0); device->BindTexture(IGLDevice::Texture2D, 0); } void GLVoxelModel::RenderDynamicLightPass(std::vector params, std::vector lights) { SPADES_MARK_FUNCTION(); device->ActiveTexture(0); device->Enable(IGLDevice::CullFace, true); device->Enable(IGLDevice::DepthTest, true); dlightProgram->Use(); static GLDynamicLightShader dlightShader; static GLProgramUniform fogDistance("fogDistance"); fogDistance(dlightProgram); fogDistance.SetValue(renderer->GetFogDistance()); static GLProgramUniform modelOrigin("modelOrigin"); modelOrigin(dlightProgram); modelOrigin.SetValue(origin.x, origin.y, origin.z); static GLProgramUniform sunLightDirection("sunLightDirection"); sunLightDirection(dlightProgram); Vector3 sunPos = MakeVector3(0, -1, -1); sunPos = sunPos.Normalize(); sunLightDirection.SetValue(sunPos.x, sunPos.y, sunPos.z); static GLProgramUniform viewOriginVector("viewOriginVector"); viewOriginVector(dlightProgram); const auto &viewOrigin = renderer->GetSceneDef().viewOrigin; viewOriginVector.SetValue(viewOrigin.x, viewOrigin.y, viewOrigin.z); // setup attributes static GLProgramAttribute positionAttribute("positionAttribute"); static GLProgramAttribute colorAttribute("colorAttribute"); static GLProgramAttribute normalAttribute("normalAttribute"); positionAttribute(dlightProgram); colorAttribute(dlightProgram); normalAttribute(dlightProgram); device->BindBuffer(IGLDevice::ArrayBuffer, buffer); device->VertexAttribPointer(positionAttribute(), 4, IGLDevice::UnsignedByte, false, sizeof(Vertex), (void *)0); device->VertexAttribPointer(colorAttribute(), 4, IGLDevice::UnsignedByte, true, sizeof(Vertex), (void *)8); device->VertexAttribPointer(normalAttribute(), 3, IGLDevice::Byte, false, sizeof(Vertex), (void *)12); device->BindBuffer(IGLDevice::ArrayBuffer, 0); device->EnableVertexAttribArray(positionAttribute(), true); device->EnableVertexAttribArray(colorAttribute(), true); device->EnableVertexAttribArray(normalAttribute(), true); device->BindBuffer(IGLDevice::ElementArrayBuffer, idxBuffer); for (size_t i = 0; i < params.size(); i++) { const client::ModelRenderParam ¶m = params[i]; // frustrum cull float rad = radius; rad *= param.matrix.GetAxis(0).GetLength(); if (!renderer->SphereFrustrumCull(param.matrix.GetOrigin(), rad)) { continue; } static GLProgramUniform customColor("customColor"); customColor(dlightProgram); customColor.SetValue(param.customColor.x, param.customColor.y, param.customColor.z); Matrix4 modelMatrix = param.matrix; static GLProgramUniform projectionViewModelMatrix("projectionViewModelMatrix"); projectionViewModelMatrix(dlightProgram); projectionViewModelMatrix.SetValue(renderer->GetProjectionViewMatrix() * modelMatrix); static GLProgramUniform viewModelMatrix("viewModelMatrix"); viewModelMatrix(dlightProgram); viewModelMatrix.SetValue(renderer->GetViewMatrix() * modelMatrix); static GLProgramUniform modelMatrixU("modelMatrix"); modelMatrixU(dlightProgram); modelMatrixU.SetValue(modelMatrix); modelMatrix.m[12] = 0.f; modelMatrix.m[13] = 0.f; modelMatrix.m[14] = 0.f; static GLProgramUniform modelNormalMatrix("modelNormalMatrix"); modelNormalMatrix(dlightProgram); modelNormalMatrix.SetValue(modelMatrix); if (param.depthHack) { device->DepthRange(0.f, 0.1f); } for (size_t i = 0; i < lights.size(); i++) { if (!GLDynamicLightShader::SphereCull(lights[i], param.matrix.GetOrigin(), rad)) continue; dlightShader(renderer, dlightProgram, lights[i], 0); device->DrawElements(IGLDevice::Triangles, numIndices, IGLDevice::UnsignedInt, (void *)0); } if (param.depthHack) { device->DepthRange(0.f, 1.f); } } device->BindBuffer(IGLDevice::ElementArrayBuffer, 0); device->EnableVertexAttribArray(positionAttribute(), false); device->EnableVertexAttribArray(colorAttribute(), false); device->EnableVertexAttribArray(normalAttribute(), false); device->ActiveTexture(0); } } }