From 3cf8a1fa944ccce44cece51b372c49b7b48d3c3f Mon Sep 17 00:00:00 2001 From: gm-matthew <108370479+gm-matthew@users.noreply.github.com> Date: Fri, 5 Jul 2024 15:57:25 +0100 Subject: [PATCH] Implementing texture NP values For some reason Model 3 uses vertex coordinates rather than texel coordinates to calculate mipmap levels Revised microtexture implementation; results are very close to real hardware when running at native resolution with supersampling disabled --- Src/Graphics/New3D/Model.h | 6 +- Src/Graphics/New3D/New3D.cpp | 10 +++- Src/Graphics/New3D/New3D.h | 1 + Src/Graphics/New3D/PolyHeader.cpp | 5 ++ Src/Graphics/New3D/PolyHeader.h | 5 +- Src/Graphics/New3D/R3DShader.cpp | 10 ++-- Src/Graphics/New3D/R3DShader.h | 4 +- Src/Graphics/New3D/R3DShaderCommon.h | 80 +++++++++++-------------- Src/Graphics/New3D/R3DShaderQuads.h | 11 +++- Src/Graphics/New3D/R3DShaderTriangles.h | 6 +- 10 files changed, 79 insertions(+), 59 deletions(-) diff --git a/Src/Graphics/New3D/Model.h b/Src/Graphics/New3D/Model.h index 6707733..795831b 100644 --- a/Src/Graphics/New3D/Model.h +++ b/Src/Graphics/New3D/Model.h @@ -52,6 +52,7 @@ struct R3DPoly Vertex v[4]; // just easier to have them as an array float faceNormal[3]; // we need this to help work out poly winding, i assume the h/w uses this instead of calculating normals itself UINT8 faceColour[4]; // per face colour + float textureNP; int number = 4; }; @@ -59,6 +60,7 @@ struct FVertex : Vertex // full vertex including face attributes { float faceNormal[3]; UINT8 faceColour[4]; + float textureNP; FVertex& operator=(const Vertex& vertex) { @@ -71,6 +73,7 @@ struct FVertex : Vertex // full vertex including face attributes { for (int i = 0; i < 4; i++) { faceColour[i] = r3dPoly.faceColour[i]; } for (int i = 0; i < 3; i++) { faceNormal[i] = r3dPoly.faceNormal[i]; } + textureNP = r3dPoly.textureNP; *this = r3dPoly.v[index]; } @@ -82,6 +85,7 @@ struct FVertex : Vertex // full vertex including face attributes // copy face attributes for (int i = 0; i < 4; i++) { faceColour[i] = r3dPoly.faceColour[i]; } for (int i = 0; i < 3; i++) { faceNormal[i] = r3dPoly.faceNormal[i]; } + textureNP = r3dPoly.textureNP; } static void Average(const FVertex& p1, const FVertex& p2, FVertex& p3) @@ -142,7 +146,7 @@ struct Mesh // microtexture bool microTexture = false; int microTextureID = 0; - float microTextureScale = 0; + float microTextureMinLOD = 0; // attributes bool textured = false; diff --git a/Src/Graphics/New3D/New3D.cpp b/Src/Graphics/New3D/New3D.cpp index 6abe615..4f01be3 100644 --- a/Src/Graphics/New3D/New3D.cpp +++ b/Src/Graphics/New3D/New3D.cpp @@ -55,6 +55,7 @@ CNew3D::CNew3D(const Util::Config::Node &config, const std::string& gameName) : glEnableVertexAttribArray(m_r3dShader.GetVertexAttribPos("inColour")); glEnableVertexAttribArray(m_r3dShader.GetVertexAttribPos("inFaceNormal")); glEnableVertexAttribArray(m_r3dShader.GetVertexAttribPos("inFixedShade")); + glEnableVertexAttribArray(m_r3dShader.GetVertexAttribPos("inTextureNP")); // before draw, specify vertex and index arrays with their offsets, offsetof is maybe evil .. glVertexAttribPointer(m_r3dShader.GetVertexAttribPos("inVertex"), 4, GL_FLOAT, GL_FALSE, sizeof(FVertex), 0); @@ -63,6 +64,7 @@ CNew3D::CNew3D(const Util::Config::Node &config, const std::string& gameName) : glVertexAttribPointer(m_r3dShader.GetVertexAttribPos("inColour"), 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(FVertex), (void*)offsetof(FVertex, faceColour)); glVertexAttribPointer(m_r3dShader.GetVertexAttribPos("inFaceNormal"), 3, GL_FLOAT, GL_FALSE, sizeof(FVertex), (void*)offsetof(FVertex, faceNormal)); glVertexAttribPointer(m_r3dShader.GetVertexAttribPos("inFixedShade"), 1, GL_FLOAT, GL_FALSE, sizeof(FVertex), (void*)offsetof(FVertex, fixedShade)); + glVertexAttribPointer(m_r3dShader.GetVertexAttribPos("inTextureNP"), 1, GL_FLOAT, GL_FALSE, sizeof(FVertex), (void*)offsetof(FVertex, textureNP)); glBindVertexArray(0); m_vbo.Bind(false); @@ -102,10 +104,12 @@ void CNew3D::SetStepping(int stepping) if (m_step > 0x10) { m_offset = 0; // culling nodes are 10 words m_vertexFactor = (1.0f / 2048.0f); // vertices are in 13.11 format + m_textureNPFactor = (1.0f / 16384.0f); // texture NP values are in 10.14 format } else { m_offset = 2; // 8 words m_vertexFactor = (1.0f / 128.0f); // 17.7 + m_textureNPFactor = (1.0f / 4096.0f); // 12.12 } } @@ -1287,10 +1291,8 @@ void CNew3D::SetMeshValues(SortingMesh *currentMesh, PolyHeader &ph) if (currentMesh->microTexture) { - static const float microTexScale[] = { 2.f, 4.f, 16.f, 256.f }; - currentMesh->microTextureID = ph.MicroTextureID(); - currentMesh->microTextureScale = microTexScale[ph.MicroTextureMinLOD()]; + currentMesh->microTextureMinLOD = (float)ph.MicroTextureMinLOD(); } } } @@ -1396,6 +1398,8 @@ void CNew3D::CacheModel(Model *m, const UINT32 *data) p.v[i].normal[2] = p.faceNormal[2]; } + p.textureNP = ph.TextureNP() * m_textureNPFactor; + UINT32* vData = ph.StartOfData(); // vertex data starts here // remaining vertices are new and defined here diff --git a/Src/Graphics/New3D/New3D.h b/Src/Graphics/New3D/New3D.h index 90f4065..614e2d6 100644 --- a/Src/Graphics/New3D/New3D.h +++ b/Src/Graphics/New3D/New3D.h @@ -244,6 +244,7 @@ private: int m_step; int m_offset; // offset to subtract for words 3 and higher of culling nodes float m_vertexFactor; // fixed-point conversion factor for vertices + float m_textureNPFactor; // fixed-point conversion factor for texture NP values // Memory (passed from outside) const UINT32 *m_cullingRAMLo; // 4 MB diff --git a/Src/Graphics/New3D/PolyHeader.cpp b/Src/Graphics/New3D/PolyHeader.cpp index 543e9c1..4274074 100644 --- a/Src/Graphics/New3D/PolyHeader.cpp +++ b/Src/Graphics/New3D/PolyHeader.cpp @@ -306,6 +306,11 @@ int PolyHeader::Y() return y; } +float PolyHeader::TextureNP() +{ + return (float)(header[5] >> 8); +} + // // header 6 // diff --git a/Src/Graphics/New3D/PolyHeader.h b/Src/Graphics/New3D/PolyHeader.h index b6b572e..c10f339 100644 --- a/Src/Graphics/New3D/PolyHeader.h +++ b/Src/Graphics/New3D/PolyHeader.h @@ -55,7 +55,7 @@ xxxxxxxx xxxxxxxx xxxxxxxx -------- Color (RGB888 or two 12-bit indexes, sensor -------- -------- -------- ---xxxxx Upper 5 bits of texture U coordinate 0x05 : -xxxxxxxx xxxxxxxx xxxxxxxx -------- Texture NP ? +xxxxxxxx xxxxxxxx xxxxxxxx -------- Texture NP -------- -------- -------- x------- Low bit of texture U coordinate -------- -------- -------- ---xxxxx Low 5 bits of texture V coordinate @@ -115,7 +115,7 @@ public: bool TexVMirror(); bool MicroTexture(); int MicroTextureID(); - int MicroTextureMinLOD(); // basically how many times it repeats compared to the base texture (i assume) + int MicroTextureMinLOD(); // header 3 int TexWidth(); @@ -133,6 +133,7 @@ public: // header 5 int X(); int Y(); + float TextureNP(); //header 6 bool Layered(); diff --git a/Src/Graphics/New3D/R3DShader.cpp b/Src/Graphics/New3D/R3DShader.cpp index fe7c302..5855b5d 100644 --- a/Src/Graphics/New3D/R3DShader.cpp +++ b/Src/Graphics/New3D/R3DShader.cpp @@ -36,7 +36,7 @@ void R3DShader::Start() m_nodeAlpha = 1.0f; m_shininess = 0; m_specularValue = 0; - m_microTexScale = 0; + m_microTexMinLOD = 0; m_microTexID = -1; m_texturePage = -1; @@ -108,7 +108,7 @@ bool R3DShader::LoadShader(const char* vertexShader, const char* fragmentShader) m_locTexture2Enabled = glGetUniformLocation(m_shaderProgram, "microTexture"); m_locTextureAlpha = glGetUniformLocation(m_shaderProgram, "textureAlpha"); m_locAlphaTest = glGetUniformLocation(m_shaderProgram, "alphaTest"); - m_locMicroTexScale = glGetUniformLocation(m_shaderProgram, "microTextureScale"); + m_locMicroTexMinLOD = glGetUniformLocation(m_shaderProgram, "microTextureMinLOD"); m_locMicroTexID = glGetUniformLocation(m_shaderProgram, "microTextureID"); m_locBaseTexInfo = glGetUniformLocation(m_shaderProgram, "baseTexInfo"); m_locBaseTexType = glGetUniformLocation(m_shaderProgram, "baseTexType"); @@ -224,9 +224,9 @@ void R3DShader::SetMeshUniforms(const Mesh* m) m_texturePage = (m->page ^ m_transPage); } - if (m_dirtyMesh || m->microTextureScale != m_microTexScale) { - glUniform1f(m_locMicroTexScale, m->microTextureScale); - m_microTexScale = m->microTextureScale; + if (m_dirtyMesh || m->microTextureMinLOD != m_microTexMinLOD) { + glUniform1f(m_locMicroTexMinLOD, m->microTextureMinLOD); + m_microTexMinLOD = m->microTextureMinLOD; } if (m_dirtyMesh || m->microTextureID != m_microTexID) { diff --git a/Src/Graphics/New3D/R3DShader.h b/Src/Graphics/New3D/R3DShader.h index ac849fb..610b8d0 100644 --- a/Src/Graphics/New3D/R3DShader.h +++ b/Src/Graphics/New3D/R3DShader.h @@ -46,7 +46,7 @@ private: GLint m_locTexturePage; GLint m_locTextureAlpha; GLint m_locAlphaTest; - GLint m_locMicroTexScale; + GLint m_locMicroTexMinLOD; GLint m_locMicroTexID; GLint m_locBaseTexInfo; GLint m_locBaseTexType; @@ -73,7 +73,7 @@ private: bool m_layered; bool m_noLosReturn; - float m_microTexScale; + float m_microTexMinLOD; int m_microTexID; int m_baseTexInfo[4]; int m_baseTexType; diff --git a/Src/Graphics/New3D/R3DShaderCommon.h b/Src/Graphics/New3D/R3DShaderCommon.h index 44cddc7..ec335d8 100644 --- a/Src/Graphics/New3D/R3DShaderCommon.h +++ b/Src/Graphics/New3D/R3DShaderCommon.h @@ -119,13 +119,13 @@ ivec2 GetMicroTexturePos(int id) return ivec2(xCoords[id],yCoords[id]); } -float mip_map_level(in vec2 texture_coordinate) // in texel units +float mip_map_level(in vec3 coordinate) { - vec2 dx_vtc = dFdx(texture_coordinate); - vec2 dy_vtc = dFdy(texture_coordinate); - float delta_max_sqr = max(dot(dx_vtc, dx_vtc), dot(dy_vtc, dy_vtc)); - float mml = 0.5 * log2(delta_max_sqr); - return max( 0.0, mml ); + // Real3D uses vertex coordinates rather than texel coordinates to calculate mipmap levels + vec3 dx_vtc = dFdx(coordinate); + vec3 dy_vtc = dFdy(coordinate); + float delta_max_sqr = max(dot(dx_vtc, dx_vtc), dot(dy_vtc, dy_vtc)); + return log2(delta_max_sqr / (fsTextureNP * fsTextureNP)) * 0.5; // result not clamped } float LinearTexLocations(int wrapMode, float size, float u, out float u0, out float u1) @@ -207,59 +207,51 @@ vec4 texBiLinear(usampler2D texSampler, ivec2 wrapMode, vec2 texSize, ivec2 texP return mix( pInterp_q0, pInterp_q1, b ); // Interpolate in Y direction. } -vec4 textureR3D(usampler2D texSampler, ivec2 wrapMode, ivec2 texSize, ivec2 texPos, vec2 texCoord) +vec4 GetTextureValue() { - float numLevels = floor(log2(min(float(texSize.x), float(texSize.y)))); // r3d only generates down to 1:1 for square textures, otherwise its the min dimension - float fLevel = min(mip_map_level(texCoord * vec2(texSize)), numLevels); - - if(alphaTest) fLevel *= 0.5; - else fLevel *= 0.8; + float lod = mip_map_level(fsViewVertex); + float numLevels = floor(log2(min(float(baseTexInfo.z), float(baseTexInfo.w)))); // r3d only generates down to 1:1 for square textures, otherwise its the min dimension + float fLevel = clamp(lod, 0.0, numLevels); int iLevel = int(fLevel); - ivec2 texPos0 = GetTexturePosition(iLevel,texPos); - ivec2 texSize0 = GetTextureSize(iLevel, texSize); + ivec2 tex1Pos = GetTexturePosition(iLevel, ivec2(baseTexInfo.xy)); + ivec2 tex1Size = GetTextureSize(iLevel, ivec2(baseTexInfo.zw)); + vec4 tex1Data = texBiLinear(textureBank[texturePage], textureWrapMode, vec2(tex1Size), tex1Pos, fsTexCoord, iLevel); - if (fLevel > 0) + // init second texel with blank data to avoid any potentially undefined behavior + vec4 tex2Data = vec4(0.0); + + float blendFactor = 0.0; + + // if LOD < 0, no need to blend with next mipmap level; slight performance boost + if (lod > 0.0) { - ivec2 texPos1 = GetTexturePosition(iLevel+1,texPos); - ivec2 texSize1 = GetTextureSize(iLevel+1, texSize); + ivec2 tex2Pos = GetTexturePosition(iLevel+1, ivec2(baseTexInfo.xy)); + ivec2 tex2Size = GetTextureSize(iLevel+1, ivec2(baseTexInfo.zw)); + tex2Data = texBiLinear(textureBank[texturePage], textureWrapMode, vec2(tex2Size), tex2Pos, fsTexCoord, iLevel+1); - vec4 texLevel0 = texBiLinear(texSampler, wrapMode, vec2(texSize0), texPos0, texCoord, iLevel); - vec4 texLevel1 = texBiLinear(texSampler, wrapMode, vec2(texSize1), texPos1, texCoord, iLevel+1); - - return mix(texLevel0, texLevel1, fract(fLevel)); // linear blend between our mipmap levels + blendFactor = fract(fLevel); } - else + else if (microTexture && lod < -microTextureMinLOD) { - // if fLevel is 0, no need to mix with next mipmap level; slight performance boost - return texBiLinear(texSampler, wrapMode, vec2(texSize0), texPos0, texCoord, iLevel); - } -} + vec4 scaleIndex = vec4(2.0, 4.0, 16.0, 256.0); // unsure if minLOD=4 has 256x scale? No games appear to use it + vec2 scale = (vec2(baseTexInfo.zw) / 128.0) * scaleIndex[int(microTextureMinLOD)]; -vec4 GetTextureValue() -{ - vec4 tex1Data = textureR3D(textureBank[texturePage], textureWrapMode, ivec2(baseTexInfo.zw), ivec2(baseTexInfo.xy), fsTexCoord); + // microtextures are always 128x128 and only use LOD 0 mipmap + ivec2 tex2Pos = GetMicroTexturePos(microTextureID); + tex2Data = texBiLinear(textureBank[(texturePage+1)&1], ivec2(0), ivec2(128), tex2Pos, fsTexCoord * scale, 0); + + blendFactor = -(lod + microTextureMinLOD) * 0.25; + blendFactor = clamp(blendFactor, 0.0, 0.5); + } + + tex1Data = mix(tex1Data, tex2Data, blendFactor); if(textureInverted) { tex1Data.rgb = vec3(1.0) - vec3(tex1Data.rgb); } - if (microTexture) { - vec2 scale = (vec2(baseTexInfo.zw) / 128.0) * microTextureScale; - ivec2 pos = GetMicroTexturePos(microTextureID); - - vec4 tex2Data = textureR3D(textureBank[(texturePage+1)&1], ivec2(0), ivec2(128), pos, fsTexCoord * scale); - - float lod = mip_map_level(fsTexCoord * scale * vec2(128.0)); - - float blendFactor = max(lod - 1.5, 0.0); // bias -1.5 - blendFactor = min(blendFactor, 1.0); // clamp to max value 1 - blendFactor = (blendFactor + 1.0) / 2.0; // 0.5 - 1 range - - tex1Data = mix(tex2Data, tex1Data, blendFactor); - } - if (alphaTest) { if (tex1Data.a < (32.0/255.0)) { discard; diff --git a/Src/Graphics/New3D/R3DShaderQuads.h b/Src/Graphics/New3D/R3DShaderQuads.h index 5ae1dee..33866c7 100644 --- a/Src/Graphics/New3D/R3DShaderQuads.h +++ b/Src/Graphics/New3D/R3DShaderQuads.h @@ -19,6 +19,7 @@ in vec2 inTexCoord; in vec3 inFaceNormal; // used to emulate r3d culling in float inFixedShade; in vec4 inColour; +in float inTextureNP; // outputs to geometry shader @@ -29,6 +30,7 @@ out VS_OUT vec2 texCoord; vec4 color; float fixedShade; + float textureNP; float discardPoly; // can't have varying bool (glsl spec) } vs_out; @@ -62,6 +64,7 @@ void main(void) vs_out.color = GetColour(inColour); vs_out.texCoord = inTexCoord; vs_out.fixedShade = inFixedShade; + vs_out.textureNP = inTextureNP * modelScale; gl_Position = projMat * modelMat * inVertex; } )glsl"; @@ -80,6 +83,7 @@ in VS_OUT vec2 texCoord; vec4 color; float fixedShade; + float textureNP; float discardPoly; // can't have varying bool (glsl spec) } gs_in[4]; @@ -95,6 +99,7 @@ out GS_OUT flat vec2 texCoord[4]; flat vec4 color; flat float fixedShade[4]; + flat float textureNP; } gs_out; //a*b - c*d, computed in a stable fashion (Kahan) @@ -128,6 +133,7 @@ void main(void) // flat attributes gs_out.color = gs_in[0].color; + gs_out.textureNP = gs_in[0].textureNP; // precompute crossproducts for all vertex combinations to be looked up in loop below for area computation precise float cross[4][4]; @@ -180,7 +186,7 @@ uniform usampler2D textureBank[2]; // entire texture sheet // texturing uniform bool textureEnabled; uniform bool microTexture; -uniform float microTextureScale; +uniform float microTextureMinLOD; uniform int microTextureID; uniform ivec4 baseTexInfo; // x/y are x,y positions in the texture sheet. z/w are with and height uniform int baseTexType; @@ -231,6 +237,7 @@ in GS_OUT flat vec2 texCoord[4]; flat vec4 color; flat float fixedShade[4]; + flat float textureNP; } fs_in; //our calculated vertex attributes from the above @@ -239,6 +246,7 @@ vec3 fsViewNormal; vec2 fsTexCoord; float fsFixedShade; vec4 fsColor; +float fsTextureNP; //outputs layout(location = 0) out vec4 out0; // opaque @@ -323,6 +331,7 @@ void QuadraticInterpolation() fsTexCoord = vec2(0.0); fsFixedShade = 0.0; fsColor = fs_in.color; + fsTextureNP = fs_in.textureNP; for (int i=0; i<4; i++) { fsViewVertex += lambda[i] * fs_in.viewVertex[i]; diff --git a/Src/Graphics/New3D/R3DShaderTriangles.h b/Src/Graphics/New3D/R3DShaderTriangles.h index f8971bd..e6968d7 100644 --- a/Src/Graphics/New3D/R3DShaderTriangles.h +++ b/Src/Graphics/New3D/R3DShaderTriangles.h @@ -19,6 +19,7 @@ in vec2 inTexCoord; in vec4 inColour; in vec3 inFaceNormal; // used to emulate r3d culling in float inFixedShade; +in float inTextureNP; // outputs to fragment shader out vec3 fsViewVertex; @@ -27,6 +28,7 @@ out vec2 fsTexCoord; out vec4 fsColor; out float fsDiscard; // can't have varying bool (glsl spec) out float fsFixedShade; +out float fsTextureNP; vec4 GetColour(vec4 colour) { @@ -58,6 +60,7 @@ void main(void) fsColor = GetColour(inColour); fsTexCoord = inTexCoord; fsFixedShade = inFixedShade; + fsTextureNP = inTextureNP * modelScale; gl_Position = projMat * modelMat * inVertex; } )glsl"; @@ -71,7 +74,7 @@ uniform usampler2D textureBank[2]; // entire texture sheet // texturing uniform bool textureEnabled; uniform bool microTexture; -uniform float microTextureScale; +uniform float microTextureMinLOD; uniform int microTextureID; uniform ivec4 baseTexInfo; // x/y are x,y positions in the texture sheet. z/w are with and height uniform int baseTexType; @@ -115,6 +118,7 @@ in vec4 fsColor; in vec2 fsTexCoord; in float fsDiscard; in float fsFixedShade; +in float fsTextureNP; //outputs layout(location = 0) out vec4 out0; // opaque