/* 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 #include #include #include #include #include #include #include "GLFramebufferManager.h" #include "GLImage.h" #include "GLProfiler.h" #include "GLProgram.h" #include "GLProgramAttribute.h" #include "GLProgramUniform.h" #include "GLRenderer.h" #include "GLShadowShader.h" #include "GLWaterRenderer.h" #include "IGLDevice.h" namespace spades { namespace draw { #pragma mark - Wave Tank Simulation class GLWaterRenderer::IWaveTank : public ConcurrentDispatch { protected: float dt; int size, samples; private: uint32_t *bitmap; int Encode8bit(float v) { v = (v + 1.f) * .5f * 255.f; v = floorf(v + .5f); int i = (int)v; if (i < 0) i = 0; if (i > 255) i = 255; return i; } uint32_t MakeBitmapPixel(float dx, float dy, float h) { float x = dx, y = dy, z = 0.04f; float scale = 200.f; x *= scale; y *= scale; z *= scale; uint32_t out; out = Encode8bit(z); out |= Encode8bit(y) << 8; out |= Encode8bit(x) << 16; out |= Encode8bit(h * -10.f) << 24; return out; } void MakeBitmapRow(float *h1, float *h2, float *h3, uint32_t *out) { out[0] = MakeBitmapPixel(h2[1] - h2[size - 1], h3[0] - h1[0], h2[0]); out[size - 1] = MakeBitmapPixel(h2[0] - h2[size - 2], h3[size - 1] - h1[size - 1], h2[size - 1]); for (int x = 1; x < size - 1; x++) { out[x] = MakeBitmapPixel(h2[x + 1] - h2[x - 1], h3[x] - h1[x], h2[x]); } } public: IWaveTank(int size) : size(size) { bitmap = new uint32_t[size * size]; samples = size * size; } virtual ~IWaveTank() { delete[] bitmap; } void SetTimeStep(float dt) { this->dt = dt; } int GetSize() const { return size; } uint32_t *GetBitmap() const { return bitmap; } void MakeBitmap(float *height) { MakeBitmapRow(height + (size - 1) * size, height, height + size, bitmap); MakeBitmapRow(height + (size - 2) * size, height + (size - 1) * size, height, bitmap + (size - 1) * size); for (int y = 1; y < size - 1; y++) { MakeBitmapRow(height + (y - 1) * size, height + y * size, height + (y + 1) * size, bitmap + y * size); } } }; #pragma mark - FFT Wave Solver struct SinCosTable { float sinCoarse[256]; float cosCoarse[256]; float sinFine[256]; float cosFine[256]; public: SinCosTable() { for (int i = 0; i < 256; i++) { float ang = (float)i / 256.f * (float)M_PI * 2.f; sinCoarse[i] = sinf(ang); cosCoarse[i] = cosf(ang); ang = (float)i / 65536.f * (float)M_PI * 2.f; sinFine[i] = sinf(ang); cosFine[i] = cosf(ang); } } void Compute(unsigned int step, float &outSin, float &outCos) { step &= 0xffff; if (step == 0) { outSin = 0; outCos = 1.f; return; } int fine = step & 0xff; int coarse = step >> 8; outSin = sinCoarse[coarse]; outCos = cosCoarse[coarse]; if (fine != 0) { float c = cosFine[fine]; float s = sinFine[fine]; float c2 = outCos * c - outSin * s; float s2 = outCos * s + outSin * c; outCos = c2; outSin = s2; } } }; static SinCosTable sinCosTable; template class GLWaterRenderer::FFTWaveTank : public IWaveTank { enum { Size = 1 << SizeBits, SizeHalf = Size / 2 }; kiss_fft_cfg fft; typedef kiss_fft_cpx Complex; struct Cell { float magnitude; uint32_t phase; float phasePerSecond; float m00, m01; float m10, m11; }; Cell cells[SizeHalf + 1][Size]; Complex spectrum[SizeHalf + 1][Size]; Complex temp1[Size]; Complex temp2[Size]; Complex temp3[Size][Size]; float height[Size][Size]; public: FFTWaveTank() : IWaveTank(Size) { fft = kiss_fft_alloc(Size, 1, NULL, NULL); for (int x = 0; x < Size; x++) { for (int y = 0; y <= SizeHalf; y++) { Cell &cell = cells[y][x]; if (x == 0 && y == 0) { cell.magnitude = 0; cell.phasePerSecond = 0.f; cell.phase = 0; } else { int cx = std::min(x, Size - x); float dist = (float)sqrtf(cx * cx + y * y); float mag = 0.8f / dist / (float)Size; mag /= dist; float scal = dist / (float)SizeHalf; scal *= scal; mag *= expf(-scal * 3.f); cell.magnitude = mag; cell.phase = mt_engine(); cell.phasePerSecond = dist * 1.e+9f * 128 / Size; } cell.m00 = GetRandom() - GetRandom(); cell.m01 = GetRandom() - GetRandom(); cell.m10 = GetRandom() - GetRandom(); cell.m11 = GetRandom() - GetRandom(); } } } virtual ~FFTWaveTank() { kiss_fft_free(fft); } virtual void Run() { // advance cells for (int x = 0; x < Size; x++) { for (int y = 0; y <= SizeHalf; y++) { Cell &cell = cells[y][x]; uint32_t dphase; dphase = (uint32_t)(cell.phasePerSecond * dt); cell.phase += dphase; unsigned int phase = cell.phase >> 16; float c, s; sinCosTable.Compute(phase, s, c); float u, v; u = c * cell.m00 + s * cell.m01; v = c * cell.m10 + s * cell.m11; spectrum[y][x].r = u * cell.magnitude; spectrum[y][x].i = v * cell.magnitude; } } // rfft for (int y = 0; y <= SizeHalf; y++) { for (int x = 0; x < Size; x++) temp1[x] = spectrum[y][x]; kiss_fft(fft, temp1, temp2); if (y == 0) { for (int x = 0; x < Size; x++) { temp3[x][0] = temp2[x]; } } else if (y == SizeHalf) { for (int x = 0; x < Size; x++) { temp3[x][SizeHalf].r = temp2[x].r; temp3[x][SizeHalf].i = 0.f; } } else { for (int x = 0; x < Size; x++) { temp3[x][y] = temp2[x]; temp3[x][Size - y].r = temp2[x].r; temp3[x][Size - y].i = -temp2[x].i; } } } for (int x = 0; x < Size; x++) { kiss_fft(fft, temp3[x], temp2); for (int y = 0; y < Size; y++) { height[x][y] = temp2[y].r; } } MakeBitmap((float *)height); } }; #pragma mark - FTCS PDE Solver class GLWaterRenderer::StandardWaveTank : public IWaveTank { float *height; float *heightFiltered; float *velocity; template void DoPDELine(float *vy, float *y1, float *y2, float *yy) { int pitch = xy ? size : 1; for (int i = 0; i < size; i++) { float v1 = *y1, v2 = *y2, v = *yy; float force = v1 + v2 - (v + v); force *= dt * 80.f; *vy += force; y1 += pitch; y2 += pitch; yy += pitch; vy += pitch; } } template void Denoise(float *arr) { int pitch = xy ? size : 1; #if 1 if ((arr[0] > 0.f && arr[(size - 1) * pitch] < 0.f && arr[pitch] < 0.f) || (arr[0] < 0.f && arr[(size - 1) * pitch] > 0.f && arr[pitch] > 0.f)) { float ttl = (arr[1] + arr[(size - 1) * pitch]) * .5f; arr[0] = ttl; } if ((arr[(size - 1) * pitch] > 0.f && arr[(size - 2) * pitch] < 0.f && arr[0] < 0.f) || (arr[(size - 1) * pitch] < 0.f && arr[(size - 2) * pitch] > 0.f && arr[0] > 0.f)) { float ttl = (arr[0] + arr[(size - 2) * pitch]) * .5f; arr[(size - 1) * pitch] = ttl; } for (int i = 1; i < size - 1; i++) { if ((arr[i * pitch] > 0.f && arr[(i - 1) * pitch] < 0.f && arr[(i + 1) * pitch] < 0.f) || (arr[i * pitch] < 0.f && arr[(i - 1) * pitch] > 0.f && arr[(i + 1) * pitch] > 0.f)) { float ttl = (arr[(i + 1) * pitch] + arr[(i - 1) * pitch]) * .5f; arr[i * pitch] = ttl; } } #else // Lax-Friedrich float buf[256]; // TODO: variable size SPAssert(size <= 256); for (int i = 0; i < size; i++) buf[i] = arr[i * pitch] * .5f; arr[0] = buf[1] + buf[size - 1]; arr[(size - 1) * pitch] = buf[size - 2] + buf[0]; for (int i = 1; i < size - 1; i++) arr[i * pitch] = buf[i - 1] + buf[i + 1]; #endif } public: StandardWaveTank(int size) : IWaveTank(size) { height = new float[size * size]; heightFiltered = new float[size * size]; velocity = new float[size * size]; std::fill(height, height + size * size, 0.f); std::fill(velocity, velocity + size * size, 0.f); } virtual ~StandardWaveTank() { delete[] height; delete[] heightFiltered; delete[] velocity; } virtual void Run() { // advance time for (int i = 0; i < samples; i++) height[i] += velocity[i] * dt; #ifndef NDEBUG for (int i = 0; i < samples; i++) SPAssert(!isnan(height[i])); for (int i = 0; i < samples; i++) SPAssert(!isnan(velocity[i])); #endif // solve ddz/dtt = c^2 (ddz/dxx + ddz/dyy) // do ddz/dyy DoPDELine(velocity, height + (size - 1) * size, height + size, height); DoPDELine(velocity + (size - 1) * size, height + (size - 2) * size, height, height + (size - 1) * size); for (int y = 1; y < size - 1; y++) { DoPDELine(velocity + y * size, height + (y - 1) * size, height + (y + 1) * size, height + y * size); } // do ddz/dxx DoPDELine(velocity, height + (size - 1), height + 1, height); DoPDELine(velocity + (size - 1), height + (size - 2), height, height + (size - 1)); for (int x = 1; x < size - 1; x++) { DoPDELine(velocity + x, height + (x - 1), height + (x + 1), height + x); } // make average 0 float sum = 0.f; for (int i = 0; i < samples; i++) sum += height[i]; sum /= (float)samples; for (int i = 0; i < samples; i++) height[i] -= sum; // limit energy sum = 0.f; for (int i = 0; i < samples; i++) { sum += height[i] * height[i]; sum += velocity[i] * velocity[i]; } sum = sqrtf(sum / (float)samples / 2.f) * 80.f; if (sum > 1.f) { sum = 1.f / sum; for (int i = 0; i < samples; i++) { height[i] *= sum; velocity[i] *= sum; } } // denoise for (int i = 0; i < size; i++) { Denoise(height + i); } for (int i = 0; i < size; i++) { Denoise(height + i * size); } // add randomness int count = (int)floorf(dt * 600.f); if (count > 400) count = 400; for (int i = 0; i < count; i++) { int ox = mt_engine() % (size - 2); int oy = mt_engine() % (size - 2); static const float gauss[] = {0.225610111284052f, 0.548779777431897f, 0.225610111284052f}; float strength = (GetRandom() - GetRandom()) * 0.15f * 100.f; for (int x = 0; x < 3; x++) for (int y = 0; y < 3; y++) { velocity[(x + ox) + (y + oy) * size] += strength * gauss[x] * gauss[y]; } } for (int i = 0; i < samples; i++) heightFiltered[i] = height[i]; // * height[i] * 100.f; // build bitmap MakeBitmap(heightFiltered); } }; #pragma mark - Water Renderer void GLWaterRenderer::PreloadShaders(spades::draw::GLRenderer *renderer) { auto &settings = renderer->GetSettings(); if ((int)settings.r_water >= 3) renderer->RegisterProgram("Shaders/Water3.program"); else if ((int)settings.r_water >= 2) renderer->RegisterProgram("Shaders/Water2.program"); else renderer->RegisterProgram("Shaders/Water.program"); } GLWaterRenderer::GLWaterRenderer(GLRenderer *renderer, client::GameMap *map) : renderer(renderer), device(renderer->GetGLDevice()), settings(renderer->GetSettings()), map(map) { SPADES_MARK_FUNCTION(); if ((int)settings.r_water >= 3) program = renderer->RegisterProgram("Shaders/Water3.program"); else if ((int)settings.r_water >= 2) program = renderer->RegisterProgram("Shaders/Water2.program"); else program = renderer->RegisterProgram("Shaders/Water.program"); BuildVertices(); tempDepthTexture = device->GenTexture(); device->BindTexture(IGLDevice::Texture2D, tempDepthTexture); device->TexImage2D(IGLDevice::Texture2D, 0, IGLDevice::DepthComponent24, device->ScreenWidth(), device->ScreenHeight(), 0, IGLDevice::DepthComponent, IGLDevice::UnsignedInt, NULL); device->TexParamater(IGLDevice::Texture2D, IGLDevice::TextureMagFilter, IGLDevice::Nearest); device->TexParamater(IGLDevice::Texture2D, IGLDevice::TextureMinFilter, IGLDevice::Nearest); device->TexParamater(IGLDevice::Texture2D, IGLDevice::TextureWrapS, IGLDevice::ClampToEdge); device->TexParamater(IGLDevice::Texture2D, IGLDevice::TextureWrapT, IGLDevice::ClampToEdge); tempFramebuffer = device->GenFramebuffer(); device->BindFramebuffer(IGLDevice::Framebuffer, tempFramebuffer); device->FramebufferTexture2D(IGLDevice::Framebuffer, IGLDevice::DepthAttachment, IGLDevice::Texture2D, tempDepthTexture, 0); // create water color texture texture = device->GenTexture(); device->BindTexture(IGLDevice::Texture2D, texture); device->TexImage2D(IGLDevice::Texture2D, 0, IGLDevice::RGBA8, map->Width(), map->Height(), 0, IGLDevice::RGBA, 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::Repeat); device->TexParamater(IGLDevice::Texture2D, IGLDevice::TextureWrapT, IGLDevice::Repeat); w = map->Width(); h = map->Height(); updateBitmapPitch = (w + 31) / 32; updateBitmap.resize(updateBitmapPitch * h); bitmap.resize(w * h); std::fill(updateBitmap.begin(), updateBitmap.end(), 0xffffffffUL); std::fill(bitmap.begin(), bitmap.end(), 0xffffffffUL); device->TexSubImage2D(IGLDevice::Texture2D, 0, 0, 0, w, h, IGLDevice::BGRA, IGLDevice::UnsignedByte, bitmap.data()); // create wave tank simlation size_t numLayers = ((int)settings.r_water >= 2) ? 3 : 1; for (size_t i = 0; i < numLayers; i++) { if ((int)settings.r_water >= 3) { waveTanks.push_back(new FFTWaveTank<8>()); } else { waveTanks.push_back(new FFTWaveTank<7>()); } waveTextures.push_back(device->GenTexture()); device->BindTexture(IGLDevice::Texture2D, waveTextures[i]); device->TexImage2D(IGLDevice::Texture2D, 0, IGLDevice::RGBA8, waveTanks[i]->GetSize(), waveTanks[i]->GetSize(), 0, IGLDevice::BGRA, IGLDevice::UnsignedByte, NULL); device->TexParamater(IGLDevice::Texture2D, IGLDevice::TextureMagFilter, IGLDevice::Linear); device->TexParamater(IGLDevice::Texture2D, IGLDevice::TextureMinFilter, IGLDevice::LinearMipmapLinear); device->TexParamater(IGLDevice::Texture2D, IGLDevice::TextureWrapS, IGLDevice::Repeat); device->TexParamater(IGLDevice::Texture2D, IGLDevice::TextureWrapT, IGLDevice::Repeat); if ((float)settings.r_maxAnisotropy > 1.1f) { device->TexParamater(IGLDevice::Texture2D, IGLDevice::TextureMaxAnisotropy, (float)settings.r_maxAnisotropy); } } occlusionQuery = 0; } struct GLWaterRenderer::Vertex { float x, y; }; void GLWaterRenderer::BuildVertices() { SPADES_MARK_FUNCTION(); std::vector vertices; std::vector indices; int meshSize = 16; if ((int)settings.r_water >= 2) meshSize = 128; float meshSizeInv = 1.f / (float)meshSize; for (int y = -meshSize; y <= meshSize; y++) { for (int x = -meshSize; x <= meshSize; x++) { Vertex v; v.x = (float)(x)*meshSizeInv; v.y = (float)(y)*meshSizeInv; // higher density near the camera v.x *= v.x * v.x; v.y *= v.y * v.y; vertices.push_back(v); } } #define VID(x, y) (((x) + meshSize) + ((y) + meshSize) * (meshSize * 2 + 1)) for (int x = -meshSize; x < meshSize; x++) { for (int y = -meshSize; y < meshSize; y++) { indices.push_back(VID(x, y)); indices.push_back(VID(x + 1, y)); indices.push_back(VID(x, y + 1)); indices.push_back(VID(x + 1, y)); indices.push_back(VID(x + 1, y + 1)); indices.push_back(VID(x, y + 1)); } } buffer = device->GenBuffer(); device->BindBuffer(IGLDevice::ArrayBuffer, buffer); device->BufferData(IGLDevice::ArrayBuffer, static_cast(sizeof(Vertex) * vertices.size()), vertices.data(), IGLDevice::StaticDraw); idxBuffer = device->GenBuffer(); device->BindBuffer(IGLDevice::ArrayBuffer, idxBuffer); device->BufferData(IGLDevice::ArrayBuffer, static_cast(sizeof(uint32_t) * indices.size()), indices.data(), IGLDevice::StaticDraw); device->BindBuffer(IGLDevice::ArrayBuffer, 0); numIndices = indices.size(); } GLWaterRenderer::~GLWaterRenderer() { SPADES_MARK_FUNCTION(); device->DeleteBuffer(buffer); device->DeleteBuffer(idxBuffer); device->DeleteFramebuffer(tempFramebuffer); device->DeleteTexture(tempDepthTexture); device->DeleteTexture(texture); if (occlusionQuery) device->DeleteQuery(occlusionQuery); for (size_t i = 0; i < waveTextures.size(); i++) { device->DeleteTexture(waveTextures[i]); waveTanks[i]->Join(); delete waveTanks[i]; } } void GLWaterRenderer::Render() { SPADES_MARK_FUNCTION(); GLProfiler profiler(device, "Render"); if (occlusionQuery == 0 && settings.r_occlusionQuery) occlusionQuery = device->GenQuery(); GLColorBuffer colorBuffer; { GLProfiler profiler(device, "Preparation"); colorBuffer = renderer->GetFramebufferManager()->PrepareForWaterRendering( tempFramebuffer, tempDepthTexture); } float fogDist = renderer->GetFogDistance(); Vector3 fogCol = renderer->GetFogColorForSolidPass(); fogCol *= fogCol; // linearize Vector3 skyCol = renderer->GetFogColor(); skyCol *= skyCol; // linearize const client::SceneDefinition &def = renderer->GetSceneDef(); float waterLevel = 63.f; float waterRange = 128.f; Matrix4 mat = Matrix4::Translate(def.viewOrigin.x, def.viewOrigin.y, waterLevel); mat = mat * Matrix4::Scale(waterRange, waterRange, 1.f); GLProfiler profiler2(device, "Draw Plane"); // do color device->DepthFunc(IGLDevice::Less); device->ColorMask(true, true, true, true); { GLProgram *prg = program; prg->Use(); static GLProgramUniform projectionViewModelMatrix("projectionViewModelMatrix"); static GLProgramUniform projectionViewMatrix("projectionViewMatrix"); static GLProgramUniform modelMatrix("modelMatrix"); static GLProgramUniform viewModelMatrix("viewModelMatrix"); static GLProgramUniform viewMatrix("viewMatrix"); static GLProgramUniform fogDistance("fogDistance"); static GLProgramUniform fogColor("fogColor"); static GLProgramUniform skyColor("skyColor"); static GLProgramUniform zNearFar("zNearFar"); static GLProgramUniform viewOrigin("viewOrigin"); static GLProgramUniform displaceScale("displaceScale"); static GLProgramUniform fovTan("fovTan"); static GLProgramUniform waterPlane("waterPlane"); projectionViewModelMatrix(prg); projectionViewMatrix(prg); modelMatrix(prg); viewModelMatrix(prg); viewMatrix(prg); fogDistance(prg); fogColor(prg); skyColor(prg); zNearFar(prg); viewOrigin(prg); displaceScale(prg); fovTan(prg); waterPlane(prg); projectionViewModelMatrix.SetValue(renderer->GetProjectionViewMatrix() * mat); projectionViewMatrix.SetValue(renderer->GetProjectionViewMatrix()); modelMatrix.SetValue(mat); viewModelMatrix.SetValue(renderer->GetViewMatrix() * mat); viewMatrix.SetValue(renderer->GetViewMatrix()); fogDistance.SetValue(fogDist); fogColor.SetValue(fogCol.x, fogCol.y, fogCol.z); skyColor.SetValue(skyCol.x, skyCol.y, skyCol.z); zNearFar.SetValue(def.zNear, def.zFar); viewOrigin.SetValue(def.viewOrigin.x, def.viewOrigin.y, def.viewOrigin.z); /*displaceScale.SetValue(1.f / renderer->ScreenWidth() / tanf(def.fovX * .5f), 1.f / renderer->ScreenHeight() / tanf(def.fovY) * .5f);*/ displaceScale.SetValue(1.f / tanf(def.fovX * .5f), 1.f / tanf(def.fovY * .5f)); fovTan.SetValue(tanf(def.fovX * .5f), -tanf(def.fovY * .5f), -tanf(def.fovX * .5f), tanf(def.fovY * .5f)); // make water plane in view coord Matrix4 wmat = renderer->GetViewMatrix() * mat; Vector3 dir = wmat.GetAxis(2); waterPlane.SetValue(dir.x, dir.y, dir.z, -Vector3::Dot(dir, wmat.GetOrigin())); static GLProgramUniform screenTexture("screenTexture"); static GLProgramUniform depthTexture("depthTexture"); static GLProgramUniform textureUnif("mainTexture"); static GLProgramUniform waveTextureUnif("waveTexture"); static GLProgramUniform waveTextureUnif1("waveTexture1"); static GLProgramUniform waveTextureUnif2("waveTexture2"); static GLProgramUniform waveTextureUnif3("waveTexture3"); static GLProgramUniform mirrorTexture("mirrorTexture"); static GLProgramUniform mirrorDepthTexture("mirrorDepthTexture"); screenTexture(prg); depthTexture(prg); textureUnif(prg); waveTextureUnif(prg); waveTextureUnif1(prg); waveTextureUnif2(prg); waveTextureUnif3(prg); mirrorTexture(prg); mirrorDepthTexture(prg); device->ActiveTexture(0); device->BindTexture(IGLDevice::Texture2D, colorBuffer.GetTexture()); screenTexture.SetValue(0); // depth is not interpolated, so color shouldn't be device->TexParamater(IGLDevice::Texture2D, IGLDevice::TextureMagFilter, IGLDevice::Nearest); device->TexParamater(IGLDevice::Texture2D, IGLDevice::TextureMinFilter, IGLDevice::Nearest); device->ActiveTexture(1); device->BindTexture(IGLDevice::Texture2D, tempDepthTexture); depthTexture.SetValue(1); device->ActiveTexture(2); device->BindTexture(IGLDevice::Texture2D, texture); textureUnif.SetValue(2); static GLShadowShader shadowShader; if (waveTextures.size() == 1) { device->ActiveTexture(3); device->BindTexture(IGLDevice::Texture2D, waveTextures[0]); waveTextureUnif.SetValue(3); shadowShader(renderer, prg, 4); } else if (waveTextures.size() == 3) { device->ActiveTexture(3); device->BindTexture(IGLDevice::Texture2D, waveTextures[0]); waveTextureUnif1.SetValue(3); // FIXME: out of texture stages; only 8 texture stages are available // (with macOS's OpenGL compatibility profile!) device->ActiveTexture(4); device->BindTexture(IGLDevice::Texture2D, waveTextures[1]); // waveTextureUnif2.SetValue(4); waveTextureUnif2.SetValue(3); device->ActiveTexture(5); device->BindTexture(IGLDevice::Texture2D, waveTextures[2]); waveTextureUnif3.SetValue(5); // mirror device->ActiveTexture(6); device->BindTexture(IGLDevice::Texture2D, renderer->GetFramebufferManager()->GetMirrorTexture()); if ((float)settings.r_maxAnisotropy > 1.1f) { device->TexParamater(IGLDevice::Texture2D, IGLDevice::TextureMaxAnisotropy, (float)settings.r_maxAnisotropy); } mirrorTexture.SetValue(6); if ((int)settings.r_water >= 3) { device->ActiveTexture(4); device->BindTexture( IGLDevice::Texture2D, renderer->GetFramebufferManager()->GetMirrorDepthTexture()); mirrorDepthTexture.SetValue(4); } shadowShader(renderer, prg, 7); } else { SPAssert(false); } static GLProgramAttribute positionAttribute("positionAttribute"); positionAttribute(prg); device->EnableVertexAttribArray(positionAttribute(), true); device->BindBuffer(IGLDevice::ArrayBuffer, buffer); device->VertexAttribPointer(positionAttribute(), 2, IGLDevice::FloatType, false, sizeof(Vertex), NULL); device->BindBuffer(IGLDevice::ArrayBuffer, 0); device->BindBuffer(IGLDevice::ElementArrayBuffer, idxBuffer); if (occlusionQuery) device->BeginQuery(IGLDevice::SamplesPassed, occlusionQuery); device->DrawElements(IGLDevice::Triangles, static_cast(numIndices), IGLDevice::UnsignedInt, NULL); if (occlusionQuery) device->EndQuery(IGLDevice::SamplesPassed); device->BindBuffer(IGLDevice::ElementArrayBuffer, 0); device->EnableVertexAttribArray(positionAttribute(), false); device->ActiveTexture(0); // restore filter mode for color buffer device->TexParamater(IGLDevice::Texture2D, IGLDevice::TextureMagFilter, IGLDevice::Linear); device->TexParamater(IGLDevice::Texture2D, IGLDevice::TextureMinFilter, IGLDevice::Linear); } } static uint32_t LinearlizeColor(uint32_t v) { int r = (uint8_t)(v); int g = (uint8_t)(v >> 8); int b = (uint8_t)(v >> 16); r = (r * r + 128) >> 8; g = (g * g + 128) >> 8; b = (b * b + 128) >> 8; return b | (g << 8) | (r << 16); } void GLWaterRenderer::Update(float dt) { SPADES_MARK_FUNCTION(); GLProfiler profiler(device, "Update"); // update wavetank simulation { GLProfiler profiler(device, "Waiting for Simulation To Done"); for (size_t i = 0; i < waveTanks.size(); i++) { waveTanks[i]->Join(); } } { { GLProfiler profiler(device, "Upload"); for (size_t i = 0; i < waveTanks.size(); i++) { device->BindTexture(IGLDevice::Texture2D, waveTextures[i]); device->TexSubImage2D(IGLDevice::Texture2D, 0, 0, 0, waveTanks[i]->GetSize(), waveTanks[i]->GetSize(), IGLDevice::BGRA, IGLDevice::UnsignedByte, waveTanks[i]->GetBitmap()); } } { GLProfiler profiler(device, "Generate Mipmap"); for (size_t i = 0; i < waveTanks.size(); i++) { device->BindTexture(IGLDevice::Texture2D, waveTextures[i]); device->GenerateMipmap(IGLDevice::Texture2D); } } } for (size_t i = 0; i < waveTanks.size(); i++) { switch (i) { case 0: waveTanks[i]->SetTimeStep(dt); break; case 1: waveTanks[i]->SetTimeStep(dt * 0.15704f / .08f); break; case 2: waveTanks[i]->SetTimeStep(dt * 0.02344f / .08f); break; } waveTanks[i]->Start(); } { GLProfiler profiler(device, "Upload Water Color Texture"); device->BindTexture(IGLDevice::Texture2D, texture); bool fullUpdate = true; for (size_t i = 0; i < updateBitmap.size(); i++) { if (updateBitmap[i] == 0) { fullUpdate = false; break; } } if (fullUpdate) { uint32_t *pixels = bitmap.data(); bool modified = false; int x = 0, y = 0; for (int i = w * h; i > 0; i--) { uint32_t col = map->GetColor(x, y, 63); x++; if (x == w) { x = 0; y++; } col = LinearlizeColor(col); if (*pixels != col) modified = true; else { pixels++; continue; } *(pixels++) = col; } if (modified) { device->TexSubImage2D(IGLDevice::Texture2D, 0, 0, 0, w, h, IGLDevice::BGRA, IGLDevice::UnsignedByte, bitmap.data()); } for (size_t i = 0; i < updateBitmap.size(); i++) { updateBitmap[i] = 0; } } else { // partial update for (size_t i = 0; i < updateBitmap.size(); i++) { int y = static_cast(i / updateBitmapPitch); int x = static_cast((i - y * updateBitmapPitch) * 32); if (updateBitmap[i] == 0) continue; uint32_t *pixels = bitmap.data() + x + y * w; bool modified = false; for (int j = 0; j < 32; j++) { uint32_t col = map->GetColor(x + j, y, 63); col = LinearlizeColor(col); if (pixels[j] != col) modified = true; else continue; pixels[j] = col; // pixels[j] = GeneratePixel(x + j, y); } if (modified) { device->TexSubImage2D(IGLDevice::Texture2D, 0, x, y, 32, 1, IGLDevice::BGRA, IGLDevice::UnsignedByte, pixels); } updateBitmap[i] = 0; } // partial update - done } } } void GLWaterRenderer::MarkUpdate(int x, int y) { x &= w - 1; y &= h - 1; updateBitmap[(x >> 5) + y * updateBitmapPitch] |= 1UL << (x & 31); } void GLWaterRenderer::GameMapChanged(int x, int y, int z, client::GameMap *map) { if (map != this->map) return; if (z < 63) return; MarkUpdate(x, y); } } }