#include "stdafx.h" #include "Sky.h" #include "VisualEngine.h" #include "TextureManager.h" #include "ShaderManager.h" #include "SceneManager.h" #include "Util.h" #include "EnvMap.h" #include "GfxCore/Geometry.h" #include "GfxCore/Device.h" #include "GfxCore/States.h" #include "v8datamodel/Sky.h" #include "g3d/LightingParameters.h" #include "GfxCore/pix.h" #include "rbx/Profiler.h" FASTFLAG(DebugRenderDownloadAssets) FASTFLAGVARIABLE(RenderMoonBillboard, true) namespace RBX { namespace Graphics { static const float kSunSize = 1000 / 2; static const float kMoonSize = 500 / 2; static const float kSkySize = 2700; static const float kStarDistance = 2500; static const int kStarTwinkleRate = 40; static const float kStarTwinkleRatio = 0.2f; static TextureRef::Status getCombinedStatus(const TextureRef (&textures)[6]) { for (int i = 0; i < 6; ++i) { TextureRef::Status status = textures[i].getStatus(); if (status != TextureRef::Status_Loaded) return status; } return TextureRef::Status_Loaded; } static Matrix4 getBillboardTransform(const RenderCamera& camera, const Vector3& position, float size) { if (FFlag::RenderMoonBillboard) { Matrix3 rotation = Math::getWellFormedRotForZVector(normalize(position - camera.getPosition())); return Matrix4(rotation * Matrix3::fromDiagonal(Vector3(size)), position); } else { Matrix4 viewInverse = camera.getViewMatrix().inverse(); return Matrix4(viewInverse.upper3x3() * Matrix3::fromDiagonal(Vector3(size)), position); } } struct SkyVertex { Vector3 position; unsigned int color; Vector2 texcoord; }; static GeometryBatch* createQuad(Device* device, const shared_ptr& layout) { SkyVertex vertices[] = { { Vector3(-1, -1, 0), 0xffffffff, Vector2(0, 1) }, { Vector3(-1, +1, 0), 0xffffffff, Vector2(0, 0) }, { Vector3(+1, -1, 0), 0xffffffff, Vector2(1, 1) }, { Vector3(+1, +1, 0), 0xffffffff, Vector2(1, 0) }, }; shared_ptr vb = device->createVertexBuffer(sizeof(SkyVertex), ARRAYSIZE(vertices), GeometryBuffer::Usage_Static); vb->upload(0, vertices, sizeof(vertices)); shared_ptr geometry = device->createGeometry(layout, vb, shared_ptr()); return new GeometryBatch(geometry, Geometry::Primitive_TriangleStrip, 4, 4); } void Sky::StarData::resize(Sky* sky, unsigned int count, bool dynamic) { if (count == 0) reset(); else { stars.resize(count); buffer = sky->visualEngine->getDevice()->createVertexBuffer(sizeof(SkyVertex), count, dynamic ? GeometryBuffer::Usage_Dynamic : GeometryBuffer::Usage_Static); batch.reset(new GeometryBatch(sky->visualEngine->getDevice()->createGeometry(sky->layout, buffer, shared_ptr()), Geometry::Primitive_Points, count, count)); } } void Sky::StarData::reset() { stars.clear(); batch.reset(); buffer.reset(); } Sky::Sky(VisualEngine* visualEngine) : visualEngine(visualEngine) , starLight(Brightness_None) , starTwinkleCounter(0) , readyState(false) { std::vector elements; elements.push_back(VertexLayout::Element(0, offsetof(SkyVertex, position), VertexLayout::Format_Float3, VertexLayout::Semantic_Position)); elements.push_back(VertexLayout::Element(0, offsetof(SkyVertex, color), VertexLayout::Format_Color, VertexLayout::Semantic_Color)); elements.push_back(VertexLayout::Element(0, offsetof(SkyVertex, texcoord), VertexLayout::Format_Float2, VertexLayout::Semantic_Texture)); layout = visualEngine->getDevice()->createVertexLayout(elements); quad.reset(createQuad(visualEngine->getDevice(), layout)); // preload default skybox so that we can show it even while fetching custom skies over HTTP if (!FFlag::DebugRenderDownloadAssets) loadSkyBoxDefault(skyBox); // preload sun/mon sun = visualEngine->getTextureManager()->load(ContentId("rbxasset://sky/sun.jpg"), TextureManager::Fallback_BlackTransparent); moon = visualEngine->getTextureManager()->load(ContentId("rbxasset://sky/moon.jpg"), TextureManager::Fallback_BlackTransparent); } Sky::~Sky() { } void Sky::update(const G3D::LightingParameters& lighting, int starCount, bool drawCelestialBodies) { skyColor = lighting.skyAmbient; skyColor2 = lighting.skyAmbient2; drawSunMoon = drawCelestialBodies; sunPosition = (lighting.physicallyCorrect ? lighting.trueSunPosition : lighting.sunPosition); sunColor = Color3(lighting.emissiveScale * .8f * G3D::clamp((sunPosition.y + 0.1f) * 10.0f, 0.f, 1.f)); moonPosition = (lighting.physicallyCorrect ? lighting.trueMoonPosition : lighting.moonPosition); moonColor = Color3(G3D::min((1.0f-lighting.skyAmbient[1])+0.1f, 1.0f)); if (!drawCelestialBodies || starCount == 0) { starsNormal.reset(); starsTwinkle.reset(); } else { Brightness oldStarLight = starLight; if (lighting.moonPosition.y <= -0.3) starLight = Brightness_None; else if (lighting.moonPosition.y <= -0.1) starLight = Brightness_Dim; else if(lighting.moonPosition.y <= 0.2) starLight = Brightness_Dimmer; else starLight = Brightness_Bright; if (starsNormal.stars.size() + starsTwinkle.stars.size() != starCount) { createStarField(starCount); updateStarsNormal(); updateStarsTwinkle(); } else if (oldStarLight != starLight) { updateStarsNormal(); updateStarsTwinkle(); } } } void Sky::prerender() { TextureRef::Status loadingStatus = getCombinedStatus(skyBoxLoading); if (loadingStatus == TextureRef::Status_Loaded) { std::copy(skyBoxLoading, skyBoxLoading + 6, skyBox); readyState = true; if (EnvMap* envMap = visualEngine->getSceneManager()->getEnvMap()) { envMap->markDirty(false); } } if (loadingStatus != TextureRef::Status_Waiting) std::fill(skyBoxLoading, skyBoxLoading + 6, TextureRef()); // Update star twinkle starTwinkleCounter++; } void Sky::render(DeviceContext* context, const RenderCamera& camera, bool drawStars) { RBXPROFILER_SCOPE("Render", "Sky"); RBXPROFILER_SCOPE("GPU", "Sky"); PIX_SCOPE(context, "Sky"); // Update load-in-progress if (starTwinkleCounter >= kStarTwinkleRate) { starTwinkleCounter = 0; if (starLight != Brightness_None && !starsTwinkle.stars.empty()) updateStarsTwinkle(); } shared_ptr program = visualEngine->getShaderManager()->getProgramOrFFP("SkyVS", "SkyFS"); if (program) { int colorHandle = program->getConstantHandle("Color"); int color2Handle = program->getConstantHandle("Color2"); context->bindProgram(program.get()); context->setRasterizerState(RasterizerState::Cull_None); context->setDepthState(DepthState(DepthState::Function_LessEqual, false)); // Render sky surface, if loaded if (1) { context->setBlendState(BlendState::Mode_None); for (int i = 0; i < 6; ++i) { PIX_SCOPE(context, "Skybox face %d", i); float adjustAngle = (i == NORM_Y) ? G3D::pif() / 2 : (i == NORM_Y_NEG) ? -G3D::pif() / 2 : 0; Matrix3 rotation = Matrix3::fromAxisAngle(Vector3(0, 1, 0), adjustAngle) * Matrix3::fromDiagonal(Vector3(-1, 1, 1)) * normalIdToMatrix3(static_cast(i)); Matrix4 transform(rotation * Matrix3::fromDiagonal(Vector3(kSkySize)), camera.getPosition() + rotation.column(2) * kSkySize); context->bindTexture(0, skyBox[i].getTexture().get() ? skyBox[i].getTexture().get() : visualEngine->getTextureManager()->getFallbackTexture(TextureManager::Fallback_White).get(), SamplerState(SamplerState::Filter_Linear, SamplerState::Address_Clamp)); float colorData[] = {skyColor.r, skyColor.g, skyColor.b, 1}; float color2Data[] = {skyColor2.r, skyColor2.g, skyColor2.b, 1 }; context->setConstant(colorHandle, colorData, 1); context->setConstant(color2Handle, color2Data, 1); context->setWorldTransforms4x3(transform[0], 1); context->draw(*quad); } } // Render sun/moon if (drawSunMoon) { PIX_SCOPE(context, "Sun/moon"); context->setBlendState(BlendState::Mode_Additive); Matrix4 sunTransform = getBillboardTransform(camera, camera.getPosition() + sunPosition * kSkySize, kSunSize); context->setConstant(colorHandle, &sunColor.r, 1); context->setConstant(color2Handle,&sunColor.r, 1); context->bindTexture(0, sun.getTexture().get(), SamplerState(SamplerState::Filter_Linear, SamplerState::Address_Clamp)); context->setWorldTransforms4x3(sunTransform[0], 1); context->draw(*quad); context->draw(*quad); context->draw(*quad); PIX_MARKER(context, "Moon:"); Matrix4 moonTransform = getBillboardTransform(camera, camera.getPosition() + moonPosition * kSkySize, kMoonSize); context->setConstant(colorHandle, &moonColor.r, 1); context->bindTexture(0, moon.getTexture().get(), SamplerState(SamplerState::Filter_Linear, SamplerState::Address_Clamp)); context->setWorldTransforms4x3(moonTransform[0], 1); context->draw(*quad); } // Render stars if (drawStars && starLight != Brightness_None && (starsNormal.batch || starsTwinkle.batch)) { PIX_SCOPE(context, "Stars"); context->setBlendState(BlendState::Mode_AlphaBlend); float colorData[] = {1, 1, 1, 1}; context->setConstant(colorHandle, colorData, 1); context->setConstant(color2Handle, colorData, 1); context->bindTexture(0, visualEngine->getTextureManager()->getFallbackTexture(TextureManager::Fallback_White).get(), SamplerState::Filter_Linear); Matrix4 transform(Matrix3::identity(), camera.getPosition()); context->setWorldTransforms4x3(transform[0], 1); if (starsNormal.batch) context->draw(*starsNormal.batch); if (starLight != Brightness_Dim && starsTwinkle.batch) context->draw(*starsTwinkle.batch); } } } void Sky::setSkyBox(const ContentId& rt, const ContentId& lf, const ContentId& bk, const ContentId& ft, const ContentId& up, const ContentId& dn) { loadSkyBox(skyBoxLoading, rt, lf, bk, ft, up, dn); } void Sky::setSkyBoxDefault() { loadSkyBoxDefault(skyBoxLoading); } void Sky::loadSkyBoxDefault(TextureRef* textures) { RBX::Sky dummy; loadSkyBox(textures, dummy.skyRt, dummy.skyLf, dummy.skyBk, dummy.skyFt, dummy.skyUp, dummy.skyDn); } void Sky::loadSkyBox(TextureRef* textures, const ContentId& rt, const ContentId& lf, const ContentId& bk, const ContentId& ft, const ContentId& up, const ContentId& dn) { TextureManager* tm = visualEngine->getTextureManager(); textures[NORM_X] = tm->load(rt, TextureManager::Fallback_None, "Sky.SkyboxRt"); textures[NORM_Y] = tm->load(up, TextureManager::Fallback_None, "Sky.SkyboxUp"); textures[NORM_Z] = tm->load(bk, TextureManager::Fallback_None, "Sky.SkyboxBk"); textures[NORM_X_NEG] = tm->load(lf, TextureManager::Fallback_None, "Sky.SkyboxLf"); textures[NORM_Y_NEG] = tm->load(dn, TextureManager::Fallback_None, "Sky.SkyboxDn"); textures[NORM_Z_NEG] = tm->load(ft, TextureManager::Fallback_None, "Sky.SkyboxFt"); } void Sky::createStarField(int starCount) { int twinkle = static_cast(starCount * kStarTwinkleRatio); int normal = starCount - twinkle; // create random positions and intensities for regular stars starsNormal.resize(this, normal, false); for (size_t i = 0; i < starsNormal.stars.size(); ++i) { Star& s = starsNormal.stars[i]; Vector3 r = Vector3::random(); s.position = r * kStarDistance; s.intensity = G3D::uniformRandom(0.3f, 0.8f); float tpos = fabs(r.y); if (tpos < 0.15f) s.intensity *= 0.6f; else if (tpos < 0.3f) s.intensity *= 0.75f; else if (tpos < 0.6f) s.intensity *= 0.9f; s.intensity *= s.intensity; } // create random positions and intensities for twinkling stars starsTwinkle.resize(this, twinkle, true); for (size_t i = 0; i < starsTwinkle.stars.size(); ++i) { Star& s = starsTwinkle.stars[i]; Vector3 r = Vector3::random(); // only top hemisphere has twinkling stars r.y = fabsf(r.y); s.position = r * kStarDistance; s.intensity = G3D::uniformRandom(0.2f, 0.7f); float tpos = fabs(r.y); if (tpos < 0.3f) s.intensity *= 0.7f; else if (tpos < 0.6f) s.intensity *= 0.9f; s.intensity *= s.intensity; } } void Sky::updateStarsNormal() { bool colorOrderBGR = visualEngine->getDevice()->getCaps().colorOrderBGR; const StarData& data = starsNormal; if (data.stars.empty()) return; SkyVertex* vertices = static_cast(data.buffer->lock()); for (unsigned int i = 0; i < data.stars.size(); ++i) { vertices[i].position = data.stars[i].position; float intensity = data.stars[i].intensity; if (starLight == Brightness_Bright) { intensity *= 5 * 0.3f; } else if (starLight == Brightness_Dimmer) { intensity *= 2.5f * 0.15f; } else { intensity *= 0.6f; } float tnum = G3D::uniformRandom(0.0, 1.0); if (tnum < 0.15) vertices[i].color = packColor(Color4(1.0, 0.80f, 0.70f, intensity), colorOrderBGR); // yellow (15%) else if (tnum < 0.55) vertices[i].color = packColor(Color4(1.0, 1.0, 1.0, intensity), colorOrderBGR); // white (40%) else if (tnum < 0.80) vertices[i].color = packColor(Color4(0.70f, 0.80f, 1.0, intensity), colorOrderBGR); // blue (25%) else vertices[i].color = packColor(Color4(0.75f, 0.50f, 1.0, intensity), colorOrderBGR); // purple (20%) vertices[i].texcoord = Vector2(0, 0); } data.buffer->unlock(); } void Sky::updateStarsTwinkle() { bool colorOrderBGR = visualEngine->getDevice()->getCaps().colorOrderBGR; const StarData& data = starsTwinkle; if (data.stars.empty()) return; SkyVertex* vertices = static_cast(data.buffer->lock(GeometryBuffer::Lock_Discard)); for (unsigned int i = 0; i < data.stars.size(); ++i) { vertices[i].position = data.stars[i].position; float randomBrightness = G3D::uniformRandom(0.3f, 1.7f); float brightfactor = (starLight == Brightness_Dimmer) ? 0.5f : 1.f; float intensity = data.stars[i].intensity * randomBrightness * brightfactor + 0.2f; float tnum = G3D::uniformRandom(0.0, 1.0); if (tnum < 0.15) vertices[i].color = packColor(Color4(1.0, 0.80f, 0.70f, intensity), colorOrderBGR); // yellow (15%) else if (tnum < 0.55) vertices[i].color = packColor(Color4(1.0, 1.0, 1.0, intensity), colorOrderBGR); // white (40%) else if (tnum < 0.80) vertices[i].color = packColor(Color4(0.70f, 0.80f, 1.0, intensity), colorOrderBGR); // blue (25%) else vertices[i].color = packColor(Color4(0.75f, 0.50f, 1.0, intensity), colorOrderBGR); // purple (20%) vertices[i].texcoord = Vector2(0, 0); } data.buffer->unlock(); } } }