#include "precompiled.h" #pragma hdrstop #include "tr_local.h" static srfTriangles_t* g_skyDomeTri = NULL; static const idMaterial* g_skyDomeMat = NULL; static idCVar r_skyDome("r_skyDome", "1", CVAR_RENDERER | CVAR_BOOL, "render procedural skydome"); static idCVar r_skyDomeRadius("r_skyDomeRadius", "8192", CVAR_RENDERER | CVAR_FLOAT, "skydome radius (world units)"); static idCVar r_skyDomeRings("r_skyDomeRings", "24", CVAR_RENDERER | CVAR_INTEGER, "skydome vertical subdivisions"); static idCVar r_skyDomeSegments("r_skyDomeSegments", "48", CVAR_RENDERER | CVAR_INTEGER, "skydome horizontal subdivisions"); static idCVar r_skyClouds("r_skyClouds", "1", CVAR_RENDERER | CVAR_BOOL, "render automatic raymarched sky clouds"); static idCVar r_skyCloudZMin("r_skyCloudZMin", "2600", CVAR_RENDERER | CVAR_FLOAT, "sky cloud slab minimum world z"); static idCVar r_skyCloudZMax("r_skyCloudZMax", "7600", CVAR_RENDERER | CVAR_FLOAT, "sky cloud slab maximum world z"); static idCVar r_skyCloudScale("r_skyCloudScale", "0.00021", CVAR_RENDERER | CVAR_FLOAT, "sky cloud world noise scale"); static idCVar r_skyCloudCoverage("r_skyCloudCoverage", "0.30", CVAR_RENDERER | CVAR_FLOAT, "sky cloud coverage threshold, lower is denser"); static idCVar r_skyCloudDensity("r_skyCloudDensity", "0.5", CVAR_RENDERER | CVAR_FLOAT, "sky cloud density accumulation"); static idCVar r_skyCloudOpacity("r_skyCloudOpacity", "0.78", CVAR_RENDERER | CVAR_FLOAT, "sky cloud final opacity"); static idCVar r_skyCloudSeedOffset("r_skyCloudSeedOffset", "12000", CVAR_RENDERER | CVAR_FLOAT, "sky cloud world noise seed offset"); static idCVar r_skyCloudColor("r_skyCloudColor", "auto", CVAR_RENDERER, "sky cloud color override as 'r g b' in 0..1 or 0..255; auto samples the skydome"); static idCVar r_skyCloudWind("r_skyCloudWind", "55 -18", CVAR_RENDERER, "sky cloud scroll wind as world x y units per second"); static idCVar r_skyCloudVolume("r_skyCloudVolume", "0.65", CVAR_RENDERER | CVAR_FLOAT, "cheap cloud self-shadow and height shaping amount"); static idCVar r_skyFog("r_skyFog", "1", CVAR_RENDERER | CVAR_BOOL, "render sky horizon fog"); static idCVar r_skyFogOpacity("r_skyFogOpacity", "0.36", CVAR_RENDERER | CVAR_FLOAT, "sky horizon fog opacity"); static idCVar r_skyFogHeight("r_skyFogHeight", "0.30", CVAR_RENDERER | CVAR_FLOAT, "view ray z height where sky horizon fog fades out"); static idCVar r_skyFogPower("r_skyFogPower", "1.15", CVAR_RENDERER | CVAR_FLOAT, "sky horizon fog falloff power"); static void R_UploadSkyDomeMeshToVertexCache(srfTriangles_t* tri) { if (!tri) { return; } if (tri->ambientCache) { vertexCache.Free(tri->ambientCache); tri->ambientCache = 0; } if (tri->indexCache) { vertexCache.Free(tri->indexCache); tri->indexCache = 0; } vertexCache.Alloc( tri->verts, tri->numVerts * sizeof(tri->verts[0]), &tri->ambientCache ); vertexCache.Alloc( tri->indexes, tri->numIndexes * sizeof(tri->indexes[0]), &tri->indexCache ); } // Build a hemisphere, top half. Inside-facing draw. static srfTriangles_t* R_BuildSkyDomeMesh(int rings, int segs, float radius) { rings = Max(rings, 4); segs = Max(segs, 8); const int vertsPerRing = segs + 1; const int numVerts = (rings + 1) * vertsPerRing; const int numQuads = rings * segs; const int numTris = numQuads * 2; const int numIndexes = numTris * 3; srfTriangles_t* tri = R_AllocStaticTriSurf(); R_AllocStaticTriSurfVerts(tri, numVerts); R_AllocStaticTriSurfIndexes(tri, numIndexes); idDrawVert* v = tri->verts; glIndex_t* idx = tri->indexes; int vi = 0; for (int r = 0; r <= rings; r++) { const float t = (float)r / (float)rings; const float phi = t * idMath::HALF_PI; const float sinPhi = idMath::Sin(phi); const float cosPhi = idMath::Cos(phi); for (int s = 0; s <= segs; s++) { const float u = (float)s / (float)segs; const float theta = u * idMath::TWO_PI; const float sinTheta = idMath::Sin(theta); const float cosTheta = idMath::Cos(theta); idVec3 dir; dir.x = cosTheta * cosPhi; dir.y = sinTheta * cosPhi; dir.z = sinPhi; v[vi].xyz = dir * radius; float uu = idMath::ATan(dir.y, dir.x) / idMath::TWO_PI + 0.5f; float vv = idMath::ASin(idMath::ClampFloat(-1.0f, 1.0f, dir.z)) / idMath::PI + 0.5f; vv = 1.0f - vv; v[vi].st.x = uu; v[vi].st.y = vv; v[vi].normal = dir; v[vi].tangents[0].Set(-sinTheta, cosTheta, 0.0f); v[vi].tangents[1].Set(0.0f, 0.0f, 1.0f); v[vi].color[0] = 255; v[vi].color[1] = 255; v[vi].color[2] = 255; v[vi].color[3] = 255; vi++; } } int ii = 0; for (int r = 0; r < rings; r++) { for (int s = 0; s < segs; s++) { const int i0 = r * vertsPerRing + s; const int i1 = i0 + 1; const int i2 = (r + 1) * vertsPerRing + s; const int i3 = i2 + 1; idx[ii++] = i0; idx[ii++] = i2; idx[ii++] = i1; idx[ii++] = i1; idx[ii++] = i2; idx[ii++] = i3; } } tri->numVerts = numVerts; tri->numIndexes = numIndexes; R_BoundTriSurf(tri); R_UploadSkyDomeMeshToVertexCache(tri); return tri; } static idImage* R_GetSkyDomeImage() { if (!g_skyDomeMat || g_skyDomeMat->GetNumStages() <= 0) { return NULL; } const shaderStage_t* stage = g_skyDomeMat->GetStage(0); if (!stage || !stage->texture.image) { return NULL; } return stage->texture.image; } static void R_GetSkyAverageColor(float color[3]) { color[0] = 0.16f; color[1] = 0.28f; color[2] = 0.48f; idImage* skyImage = R_GetSkyDomeImage(); if (!skyImage) { return; } skyImage->Bind(); const float* average = skyImage->GetAverageColor(); if (!average) { return; } color[0] = idMath::ClampFloat(0.03f, 1.0f, average[0]); color[1] = idMath::ClampFloat(0.03f, 1.0f, average[1]); color[2] = idMath::ClampFloat(0.03f, 1.0f, average[2]); } static void R_GetSkyCloudColor(float color[3]) { R_GetSkyAverageColor(color); float overrideColor[3]; if (sscanf(r_skyCloudColor.GetString(), "%f %f %f", &overrideColor[0], &overrideColor[1], &overrideColor[2]) != 3) { return; } const bool byteColor = overrideColor[0] > 1.0f || overrideColor[1] > 1.0f || overrideColor[2] > 1.0f; const float scale = byteColor ? (1.0f / 255.0f) : 1.0f; color[0] = idMath::ClampFloat(0.0f, 1.0f, overrideColor[0] * scale); color[1] = idMath::ClampFloat(0.0f, 1.0f, overrideColor[1] * scale); color[2] = idMath::ClampFloat(0.0f, 1.0f, overrideColor[2] * scale); } static void RB_DrawSkyCloudOverlay(const viewDef_t* viewDef) { if (!viewDef) { return; } const bool drawClouds = r_skyClouds.GetBool(); const bool drawFog = r_skyFog.GetBool() && r_skyFogOpacity.GetFloat() > 0.0f; if (!drawClouds && !drawFog) { return; } glSkyClouds_t clouds; memset(&clouds, 0, sizeof(clouds)); R_GetSkyCloudColor(clouds.skyColor); R_GetSkyAverageColor(clouds.fogColor); clouds.opacity = drawClouds ? idMath::ClampFloat(0.0f, 1.0f, r_skyCloudOpacity.GetFloat()) : 0.0f; clouds.fogOpacity = drawFog ? idMath::ClampFloat(0.0f, 1.0f, r_skyFogOpacity.GetFloat()) : 0.0f; clouds.fogHeight = Max(r_skyFogHeight.GetFloat(), 0.01f); clouds.fogPower = Max(r_skyFogPower.GetFloat(), 0.25f); const idVec3& viewOrg = viewDef->renderView.vieworg; clouds.viewOrigin[0] = viewOrg.x; clouds.viewOrigin[1] = viewOrg.y; clouds.viewOrigin[2] = viewOrg.z; clouds.zMin = r_skyCloudZMin.GetFloat(); clouds.zMax = Max(r_skyCloudZMax.GetFloat(), clouds.zMin + 64.0f); const idMat3& axis = viewDef->renderView.viewaxis; clouds.viewForward[0] = axis[0].x; clouds.viewForward[1] = axis[0].y; clouds.viewForward[2] = axis[0].z; clouds.viewRight[0] = -axis[1].x; clouds.viewRight[1] = -axis[1].y; clouds.viewRight[2] = -axis[1].z; clouds.viewUp[0] = axis[2].x; clouds.viewUp[1] = axis[2].y; clouds.viewUp[2] = axis[2].z; clouds.tanHalfFovX = idMath::Tan(DEG2RAD(viewDef->renderView.fov_x * 0.5f)); clouds.tanHalfFovY = idMath::Tan(DEG2RAD(viewDef->renderView.fov_y * 0.5f)); clouds.noiseScale = Max(r_skyCloudScale.GetFloat(), 0.00001f); clouds.coverage = idMath::ClampFloat(0.0f, 1.0f, r_skyCloudCoverage.GetFloat()); clouds.density = drawClouds ? Max(r_skyCloudDensity.GetFloat(), 0.0f) : 0.0f; clouds.seedOffset = r_skyCloudSeedOffset.GetFloat(); clouds.time = viewDef->renderView.time * 0.001f; clouds.wind[0] = 55.0f; clouds.wind[1] = -18.0f; sscanf(r_skyCloudWind.GetString(), "%f %f", &clouds.wind[0], &clouds.wind[1]); clouds.volume = idMath::ClampFloat(0.0f, 1.0f, r_skyCloudVolume.GetFloat()); glDrawSkyCloudsQD3D12(&clouds); } void R_InitSkyDome() { if (g_skyDomeTri) { return; } g_skyDomeTri = R_BuildSkyDomeMesh( r_skyDomeRings.GetInteger(), r_skyDomeSegments.GetInteger(), r_skyDomeRadius.GetFloat() ); } void R_ShutdownSkyDome() { if (g_skyDomeTri) { if (g_skyDomeTri->ambientCache) { vertexCache.Free(g_skyDomeTri->ambientCache); g_skyDomeTri->ambientCache = 0; } if (g_skyDomeTri->indexCache) { vertexCache.Free(g_skyDomeTri->indexCache); g_skyDomeTri->indexCache = 0; } R_FreeStaticTriSurf(g_skyDomeTri); g_skyDomeTri = NULL; } g_skyDomeMat = NULL; } static const idMaterial* R_GetSkyDomeMaterial() { return backEnd.viewDef->renderView.skyDomeMaterial; } void RB_DrawSkyDome(const viewDef_t* viewDef) { if (!r_skyDome.GetBool()) { return; } if (!g_skyDomeTri || !g_skyDomeTri->ambientCache || !g_skyDomeTri->indexCache) { return; } g_skyDomeMat = R_GetSkyDomeMaterial(); if (!g_skyDomeMat) { return; } glDepthMask(GL_FALSE); glDisable(GL_CULL_FACE); glMatrixMode(GL_MODELVIEW); glPushMatrix(); float mv[16]; memcpy(mv, backEnd.viewDef->worldSpace.modelViewMatrix, sizeof(mv)); mv[12] = 0.0f; mv[13] = 0.0f; mv[14] = 0.0f; glLoadMatrixf(mv); GL_SelectTexture(0); idImage* skyImage = R_GetSkyDomeImage(); if (skyImage) { skyImage->Bind(); } else { globalImages->whiteImage->Bind(); } idDrawVert* verts = (idDrawVert*)vertexCache.Position(g_skyDomeTri->ambientCache); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glVertexPointer( 3, GL_FLOAT, sizeof(idDrawVert), verts->xyz.ToFloatPtr() ); glTexCoordPointer( 2, GL_FLOAT, sizeof(idDrawVert), verts->st.ToFloatPtr() ); glGeometryFlagf(GEOMETRY_FLAG_UNLIT + GEOMETRY_FLAG_SKYDOME); RB_DrawElementsWithCounters(g_skyDomeTri); RB_DrawSkyCloudOverlay(viewDef); glGeometryFlagf(GEOMETRY_FLAG_NONE); glPopMatrix(); GL_State(GLS_DEFAULT); glDepthMask(GL_TRUE); glEnable(GL_CULL_FACE); glClear(GL_DEPTH_BUFFER_BIT); }