From 61b062413f2fa97f78319794078740267b8fb54b Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Sun, 10 May 2026 02:09:06 +0100 Subject: [PATCH 1/9] Added Support for TextureBuffer to use RGBA when available * Created TextureBufferReader to simplify reading Buffers * Added texture_facees.glsl which proves a `getFaceData` which returns a struct --- src/main/java/rs117/hd/HdPlugin.java | 2 + .../hd/renderer/zone/VertexWriteCache.java | 2 +- .../hd/utils/buffer/GLTextureBuffer.java | 45 ++++- .../rs117/hd/utils/buffer/GpuIntBuffer.java | 2 +- src/main/resources/rs117/hd/scene_vert.glsl | 32 +--- src/main/resources/rs117/hd/shadow_vert.glsl | 10 +- .../rs117/hd/uniforms/texture_faces.glsl | 21 +++ .../rs117/hd/utils/texture_buffer_reader.glsl | 166 ++++++++++++++++++ 8 files changed, 241 insertions(+), 39 deletions(-) create mode 100644 src/main/resources/rs117/hd/uniforms/texture_faces.glsl create mode 100644 src/main/resources/rs117/hd/utils/texture_buffer_reader.glsl diff --git a/src/main/java/rs117/hd/HdPlugin.java b/src/main/java/rs117/hd/HdPlugin.java index e08fa46dd5..bf215902de 100644 --- a/src/main/java/rs117/hd/HdPlugin.java +++ b/src/main/java/rs117/hd/HdPlugin.java @@ -128,6 +128,7 @@ import rs117.hd.utils.ResourcePath; import rs117.hd.utils.ShaderRecompile; import rs117.hd.utils.buffer.GLBuffer; +import rs117.hd.utils.buffer.GLTextureBuffer; import rs117.hd.utils.jobs.GenericJob; import rs117.hd.utils.jobs.JobSystem; @@ -901,6 +902,7 @@ public ShaderIncludes getShaderIncludes() { var includes = new ShaderIncludes() .addIncludePath(SHADER_PATH) .addInclude("VERSION_HEADER", OSType.getOSType() == OSType.Linux ? LINUX_VERSION_HEADER : WINDOWS_VERSION_HEADER) + .define("TEXEL_SIZE", GLTextureBuffer.isRGBASupported() ? 4 : 3) .define("UI_SCALING_MODE", config.uiScalingMode()) .define("COLOR_BLINDNESS", config.colorBlindness()) .define("APPLY_COLOR_FILTER", configColorFilter != ColorFilter.NONE) diff --git a/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java b/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java index b72914f70c..7807942912 100644 --- a/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java +++ b/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java @@ -46,7 +46,7 @@ public int putFace( if (stagingPosition + 9 > stagingBuffer.length) flushAndGrow(); - final int textureFaceIdx = (outputBuffer.position() + stagingPosition) / 3; + final int textureFaceIdx = outputBuffer.position() + stagingPosition; final int[] stagingBuffer = this.stagingBuffer; final int stagingPosition = this.stagingPosition; diff --git a/src/main/java/rs117/hd/utils/buffer/GLTextureBuffer.java b/src/main/java/rs117/hd/utils/buffer/GLTextureBuffer.java index 1b58183741..72bdc6e173 100644 --- a/src/main/java/rs117/hd/utils/buffer/GLTextureBuffer.java +++ b/src/main/java/rs117/hd/utils/buffer/GLTextureBuffer.java @@ -1,31 +1,44 @@ package rs117.hd.utils.buffer; import lombok.Getter; +import rs117.hd.HdPlugin; import static org.lwjgl.opengl.GL33C.*; +import static rs117.hd.HdPlugin.GL_CAPS; public class GLTextureBuffer extends GLBuffer { + + public static boolean isRGBASupported() { return HdPlugin.GL_CAPS.GL_ARB_texture_buffer_object; } + @Getter private int texId; + private final int texelSize; + private final int internalFormat; + public GLTextureBuffer(String name, int usage) { this(name, usage, 0); } public GLTextureBuffer(String name, int usage, int storageFlags) { super(name, GL_TEXTURE_BUFFER, usage, storageFlags); + + if(isRGBASupported()) { + texelSize = 4; + internalFormat = GL_RGBA32I; + } else { + texelSize = 3; + internalFormat = GL_RGB32I; + } } @Override public GLTextureBuffer initialize(long initialCapacity) { super.initialize(initialCapacity); - // Create texture texId = glGenTextures(); glBindTexture(target, texId); - - // RGB32 signed integer texture buffer - glTexBuffer(target, GL_RGB32I, id); + glTexBuffer(target, internalFormat, id); glBindTexture(target, 0); return this; @@ -33,22 +46,36 @@ public GLTextureBuffer initialize(long initialCapacity) { @Override public boolean ensureCapacity(long byteOffset, long numBytes) { + // Ensure offset and size are aligned to texels + byteOffset = alignToTexel(byteOffset); + numBytes = alignToTexel(numBytes); + int oldId = id; - boolean resized = super.ensureCapacity(byteOffset, numBytes); + final boolean resized = super.ensureCapacity(byteOffset, numBytes); + if (oldId != id) { glBindTexture(target, texId); - glTexBuffer(target, GL_RGB32I, id); + glTexBuffer(target, internalFormat, id); glBindTexture(target, 0); } + return resized; } + private long alignToTexel(long numBytes) { + final int texelBytes = texelSize * 4; + final long remainder = numBytes % texelBytes; + if (remainder == 0) + return numBytes; + + return numBytes + (texelBytes - remainder); + } + @Override public void destroy() { - if (texId != 0) { + if (texId != 0) glDeleteTextures(texId); - texId = 0; - } + texId = 0; super.destroy(); } diff --git a/src/main/java/rs117/hd/utils/buffer/GpuIntBuffer.java b/src/main/java/rs117/hd/utils/buffer/GpuIntBuffer.java index cdf5c6e79a..ff0a066e33 100644 --- a/src/main/java/rs117/hd/utils/buffer/GpuIntBuffer.java +++ b/src/main/java/rs117/hd/utils/buffer/GpuIntBuffer.java @@ -145,7 +145,7 @@ public static int putFace( int materialDataA, int materialDataB, int materialDataC, int terrainDataA, int terrainDataB, int terrainDataC ) { - final int textureFaceIdx = buffer.position() / 3; + final int textureFaceIdx = buffer.position(); buffer.put(alphaBiasHslA); buffer.put(alphaBiasHslB); buffer.put(alphaBiasHslC); diff --git a/src/main/resources/rs117/hd/scene_vert.glsl b/src/main/resources/rs117/hd/scene_vert.glsl index 06b5268582..f10bed621a 100644 --- a/src/main/resources/rs117/hd/scene_vert.glsl +++ b/src/main/resources/rs117/hd/scene_vert.glsl @@ -27,6 +27,7 @@ #include #include +#include #include #include @@ -39,8 +40,6 @@ layout (location = 0) in vec3 vPosition; layout (location = 3) in int vTextureFaceIdx; layout (location = 6) in int vWorldViewId; layout (location = 7) in ivec2 vSceneBase; - - uniform isamplerBuffer textureFaces; #else layout (location = 1) in vec3 vUv; layout (location = 2) in vec3 vNormal; @@ -67,28 +66,15 @@ layout (location = 0) in vec3 vPosition; } OUT; void main() { - int vertex = gl_VertexID % 3; - bool isProvoking = vertex == 2; - int materialData = 0; - int alphaBiasHsl = 0; + FaceData faceData = getFaceData(vTextureFaceIdx); + fAlphaBiasHsl = faceData.AlphaBiasHsl; + fMaterialData = faceData.MaterialData; + fTerrainData = faceData.TerrainData; + fWorldViewId = vWorldViewId; - if (isProvoking) { - // Only the Provoking vertex needs to fetch the face data - fAlphaBiasHsl = texelFetch(textureFaces, vTextureFaceIdx).xyz; - fMaterialData = texelFetch(textureFaces, vTextureFaceIdx + 1).xyz; - fTerrainData = texelFetch(textureFaces, vTextureFaceIdx + 2).xyz; - fWorldViewId = vWorldViewId; - alphaBiasHsl = fAlphaBiasHsl[vertex]; - materialData = fMaterialData[vertex]; - } else { - // All outputs must be written to for macOS compatibility - fAlphaBiasHsl = ivec3(0); - fMaterialData = ivec3(0); - fTerrainData = ivec3(0); - fWorldViewId = 0; - alphaBiasHsl = texelFetch(textureFaces, vTextureFaceIdx)[vertex]; - materialData = texelFetch(textureFaces, vTextureFaceIdx + 1)[vertex]; - } + int vertex = gl_VertexID % 3; + int alphaBiasHsl = faceData.AlphaBiasHsl[vertex]; + int materialData = faceData.MaterialData[vertex]; vec3 sceneOffset = vec3(vSceneBase.x, 0, vSceneBase.y); vec3 worldNormal = vNormal.xyz; diff --git a/src/main/resources/rs117/hd/shadow_vert.glsl b/src/main/resources/rs117/hd/shadow_vert.glsl index 82ead2102d..d6daf13056 100644 --- a/src/main/resources/rs117/hd/shadow_vert.glsl +++ b/src/main/resources/rs117/hd/shadow_vert.glsl @@ -28,6 +28,7 @@ #include #include #include +#include #include @@ -39,8 +40,6 @@ layout (location = 0) in vec3 vPosition; layout (location = 6) in int vWorldViewId; layout (location = 7) in ivec2 vSceneBase; - uniform isamplerBuffer textureFaces; - #if SHADOW_MODE == SHADOW_MODE_DETAILED out vec4 fUvw; flat out int fMaterialData; @@ -52,9 +51,10 @@ layout (location = 0) in vec3 vPosition; void main() { int vertex = gl_VertexID % 3; - int alphaBiasHsl = texelFetch(textureFaces, vTextureFaceIdx)[vertex]; - int materialData = texelFetch(textureFaces, vTextureFaceIdx + 1)[vertex]; - int terrainData = texelFetch(textureFaces, vTextureFaceIdx + 2)[vertex]; + FaceData faceData = getFaceData(vTextureFaceIdx); + int alphaBiasHsl = faceData.AlphaBiasHsl[vertex]; + int materialData = faceData.MaterialData[vertex]; + int terrainData = faceData.TerrainData[vertex]; int waterTypeIndex = terrainData >> 3 & 0xFF; float opacity = 1 - (alphaBiasHsl >> 24 & 0xFF) / float(0xFF); diff --git a/src/main/resources/rs117/hd/uniforms/texture_faces.glsl b/src/main/resources/rs117/hd/uniforms/texture_faces.glsl new file mode 100644 index 0000000000..0824d13808 --- /dev/null +++ b/src/main/resources/rs117/hd/uniforms/texture_faces.glsl @@ -0,0 +1,21 @@ +#pragma once + +#include + +uniform isamplerBuffer textureFaces; + +struct FaceData { + ivec3 AlphaBiasHsl; + ivec3 MaterialData; + ivec3 TerrainData; +}; + +FaceData getFaceData(int faceOffset) { + TexBufferReader reader = buildTexBufferReader(faceOffset); + + FaceData faceData; + faceData.AlphaBiasHsl = readIVec3(textureFaces, reader); + faceData.MaterialData = readIVec3(textureFaces, reader); + faceData.TerrainData = readIVec3(textureFaces, reader); + return faceData; +} \ No newline at end of file diff --git a/src/main/resources/rs117/hd/utils/texture_buffer_reader.glsl b/src/main/resources/rs117/hd/utils/texture_buffer_reader.glsl new file mode 100644 index 0000000000..dd82e4359c --- /dev/null +++ b/src/main/resources/rs117/hd/utils/texture_buffer_reader.glsl @@ -0,0 +1,166 @@ +#pragma once + +// Number of scalar components per fetched texel. +// Valid range: 1-4. +#include TEXEL_SIZE +#ifndef TEXEL_SIZE + #define TEXEL_SIZE 4 +#endif + +#if TEXEL_SIZE < 1 || TEXEL_SIZE > 4 + #error TEXEL_SIZE must be between 1 and 4 +#endif + +// Sequential reader for tightly-packed scalar data stored in a buffer texture. +// +// Layout assumptions: +// - Data is packed scalar-by-scalar with NO padding. +// - Floats are stored as IEEE-754 bit patterns inside integer components. +// - TEXEL_SIZE defines how many usable components exist per fetched texel. +// +// Example packed stream: +// [int][float][vec3][ivec2]... +// +// This reader caches the currently loaded texel to avoid redundant texelFetch +// calls during sequential access. + +struct TexBufferReader { + // Cached texel data. + // Always ivec4 regardless of TEXEL_SIZE. + ivec4 data; + + // Current scalar position in stream. + int position; + + // Currently cached texel index. + int loadedTexel; +}; + +TexBufferReader buildTexBufferReader( + int position +) { + TexBufferReader reader; + + reader.position = position; + + reader.data = ivec4(0); + reader.loadedTexel = -1; + + return reader; +} + +int readInt(isamplerBuffer buf, inout TexBufferReader reader) { +#if TEXEL_SIZE == 4 + int texelIndex = reader.position >> 2; + int component = reader.position & 3; +#else + int texelIndex = reader.position / TEXEL_SIZE; + int component = reader.position % TEXEL_SIZE; +#endif + + if (texelIndex != reader.loadedTexel) { + reader.data = texelFetch(buf, texelIndex); + reader.loadedTexel = texelIndex; + } + + reader.position++; + + switch (component) { + case 0: return reader.data.x; + case 1: return reader.data.y; + case 2: return reader.data.z; + default: return reader.data.w; + } +} + +uint readUInt(isamplerBuffer buf, inout TexBufferReader reader) { + return uint(readInt(buf, reader)); +} + +float readFloat(isamplerBuffer buf, inout TexBufferReader reader) { + return intBitsToFloat(readInt(buf, reader)); +} + +bool readBool(isamplerBuffer buf, inout TexBufferReader reader) { + return readInt(buf, reader) != 0; +} + +ivec2 readIVec2(isamplerBuffer buf, inout TexBufferReader reader) { + return ivec2( + readInt(buf, reader), + readInt(buf, reader) + ); +} + +ivec3 readIVec3(isamplerBuffer buf, inout TexBufferReader reader) { + return ivec3( + readInt(buf, reader), + readInt(buf, reader), + readInt(buf, reader) + ); +} + +ivec4 readIVec4(isamplerBuffer buf, inout TexBufferReader reader) { + return ivec4( + readInt(buf, reader), + readInt(buf, reader), + readInt(buf, reader), + readInt(buf, reader) + ); +} + +uvec2 readUVec2(isamplerBuffer buf, inout TexBufferReader reader) { + return uvec2( + readUInt(buf, reader), + readUInt(buf, reader) + ); +} + +uvec3 readUVec3(isamplerBuffer buf, inout TexBufferReader reader) { + return uvec3( + readUInt(buf, reader), + readUInt(buf, reader), + readUInt(buf, reader) + ); +} + +uvec4 readUVec4(isamplerBuffer buf, inout TexBufferReader reader) { + return uvec4( + readUInt(buf, reader), + readUInt(buf, reader), + readUInt(buf, reader), + readUInt(buf, reader) + ); +} + +vec2 readVec2(isamplerBuffer buf, inout TexBufferReader reader) { + return vec2( + readFloat(buf, reader), + readFloat(buf, reader) + ); +} + +vec3 readVec3(isamplerBuffer buf, inout TexBufferReader reader) { + return vec3( + readFloat(buf, reader), + readFloat(buf, reader), + readFloat(buf, reader) + ); +} + +vec4 readVec4(isamplerBuffer buf, inout TexBufferReader reader) { + return vec4( + readFloat(buf, reader), + readFloat(buf, reader), + readFloat(buf, reader), + readFloat(buf, reader) + ); +} + +void skipScalars(inout TexBufferReader reader, int count) { + reader.position += count; +} + +void rewindReader(inout TexBufferReader reader, int position) { + reader.position = position; +} \ No newline at end of file From 7db8ba3e37e27447ca64ea9a7ca025e11947c2d3 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Sun, 10 May 2026 02:24:05 +0100 Subject: [PATCH 2/9] Use Macros to build the parser --- .../rs117/hd/uniforms/texture_faces.glsl | 14 ++++------- .../rs117/hd/utils/texture_buffer_reader.glsl | 23 ++++++++++++++++++- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/main/resources/rs117/hd/uniforms/texture_faces.glsl b/src/main/resources/rs117/hd/uniforms/texture_faces.glsl index 0824d13808..7b0bdd0b16 100644 --- a/src/main/resources/rs117/hd/uniforms/texture_faces.glsl +++ b/src/main/resources/rs117/hd/uniforms/texture_faces.glsl @@ -10,12 +10,8 @@ struct FaceData { ivec3 TerrainData; }; -FaceData getFaceData(int faceOffset) { - TexBufferReader reader = buildTexBufferReader(faceOffset); - - FaceData faceData; - faceData.AlphaBiasHsl = readIVec3(textureFaces, reader); - faceData.MaterialData = readIVec3(textureFaces, reader); - faceData.TerrainData = readIVec3(textureFaces, reader); - return faceData; -} \ No newline at end of file +BEGIN_BUFFER_PARSER(getFaceData,FaceData) + READ_IVEC3(textureFaces, AlphaBiasHsl) + READ_IVEC3(textureFaces, MaterialData) + READ_IVEC3(textureFaces, TerrainData) +END_BUFFER_PARSER() \ No newline at end of file diff --git a/src/main/resources/rs117/hd/utils/texture_buffer_reader.glsl b/src/main/resources/rs117/hd/utils/texture_buffer_reader.glsl index dd82e4359c..ff43908d46 100644 --- a/src/main/resources/rs117/hd/utils/texture_buffer_reader.glsl +++ b/src/main/resources/rs117/hd/utils/texture_buffer_reader.glsl @@ -163,4 +163,25 @@ void skipScalars(inout TexBufferReader reader, int count) { void rewindReader(inout TexBufferReader reader, int position) { reader.position = position; -} \ No newline at end of file +} + +#define BEGIN_BUFFER_PARSER(FuncName, StructType) \ +StructType FuncName(int offset) { \ + TexBufferReader reader = \ + buildTexBufferReader(offset); \ + \ + StructType data; + +#define END_BUFFER_PARSER() \ + return data; \ +} + +#define READ_INT(texBuffer, field) data.field = readInt(texBuffer, reader); +#define READ_FLOAT(texBuffer, field) data.field = readFloat(texBuffer, reader); +#define READ_BOOL(texBuffer, field) data.field = readBool(texBuffer, reader); +#define READ_IVEC2(texBuffer, field) data.field = readIVec2(texBuffer, reader); +#define READ_IVEC3(texBuffer, field) data.field = readIVec3(texBuffer, reader); +#define READ_IVEC4(texBuffer, field) data.field = readIVec4(texBuffer, reader); +#define READ_VEC2(texBuffer, field) data.field = readVec2(texBuffer, reader); +#define READ_VEC3(texBuffer, field) data.field = readVec3(texBuffer, reader); +#define READ_VEC4(texBuffer, field) data.field = readVec4(texBuffer, reader); \ No newline at end of file From 1fe5b0d00ddef9ea9d4fb70d15114f130d898d24 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Sun, 10 May 2026 02:30:01 +0100 Subject: [PATCH 3/9] Define the buffer to remove the need to pass it into each read func --- .../rs117/hd/uniforms/texture_faces.glsl | 8 +++++--- .../rs117/hd/utils/texture_buffer_reader.glsl | 18 +++++++++--------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/main/resources/rs117/hd/uniforms/texture_faces.glsl b/src/main/resources/rs117/hd/uniforms/texture_faces.glsl index 7b0bdd0b16..ae2c64ca8c 100644 --- a/src/main/resources/rs117/hd/uniforms/texture_faces.glsl +++ b/src/main/resources/rs117/hd/uniforms/texture_faces.glsl @@ -10,8 +10,10 @@ struct FaceData { ivec3 TerrainData; }; +#define PARSER_TARGET_BUFFER textureFaces + BEGIN_BUFFER_PARSER(getFaceData,FaceData) - READ_IVEC3(textureFaces, AlphaBiasHsl) - READ_IVEC3(textureFaces, MaterialData) - READ_IVEC3(textureFaces, TerrainData) + READ_IVEC3(AlphaBiasHsl) + READ_IVEC3(MaterialData) + READ_IVEC3(TerrainData) END_BUFFER_PARSER() \ No newline at end of file diff --git a/src/main/resources/rs117/hd/utils/texture_buffer_reader.glsl b/src/main/resources/rs117/hd/utils/texture_buffer_reader.glsl index ff43908d46..d85e300788 100644 --- a/src/main/resources/rs117/hd/utils/texture_buffer_reader.glsl +++ b/src/main/resources/rs117/hd/utils/texture_buffer_reader.glsl @@ -176,12 +176,12 @@ StructType FuncName(int offset) { \ return data; \ } -#define READ_INT(texBuffer, field) data.field = readInt(texBuffer, reader); -#define READ_FLOAT(texBuffer, field) data.field = readFloat(texBuffer, reader); -#define READ_BOOL(texBuffer, field) data.field = readBool(texBuffer, reader); -#define READ_IVEC2(texBuffer, field) data.field = readIVec2(texBuffer, reader); -#define READ_IVEC3(texBuffer, field) data.field = readIVec3(texBuffer, reader); -#define READ_IVEC4(texBuffer, field) data.field = readIVec4(texBuffer, reader); -#define READ_VEC2(texBuffer, field) data.field = readVec2(texBuffer, reader); -#define READ_VEC3(texBuffer, field) data.field = readVec3(texBuffer, reader); -#define READ_VEC4(texBuffer, field) data.field = readVec4(texBuffer, reader); \ No newline at end of file +#define READ_INT(field) data.field = readInt(PARSER_TARGET_BUFFER, reader); +#define READ_FLOAT(field) data.field = readFloat(PARSER_TARGET_BUFFER, reader); +#define READ_BOOL(field) data.field = readBool(PARSER_TARGET_BUFFER, reader); +#define READ_IVEC2(field) data.field = readIVec2(PARSER_TARGET_BUFFER, reader); +#define READ_IVEC3(field) data.field = readIVec3(PARSER_TARGET_BUFFER, reader); +#define READ_IVEC4(field) data.field = readIVec4(PARSER_TARGET_BUFFER, reader); +#define READ_VEC2(field) data.field = readVec2(PARSER_TARGET_BUFFER, reader); +#define READ_VEC3(field) data.field = readVec3(PARSER_TARGET_BUFFER, reader); +#define READ_VEC4(field) data.field = readVec4(PARSER_TARGET_BUFFER, reader); \ No newline at end of file From fcad741ca1b39dd68dc45ceea65a54990230a4b2 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Sun, 10 May 2026 04:38:39 +0100 Subject: [PATCH 4/9] Dont Align to texal size underlying buffer will always be pow2, so even if its RGB or RGBA it'll fit correctly --- .../hd/utils/buffer/GLTextureBuffer.java | 26 +++---------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/src/main/java/rs117/hd/utils/buffer/GLTextureBuffer.java b/src/main/java/rs117/hd/utils/buffer/GLTextureBuffer.java index 72bdc6e173..713c993c93 100644 --- a/src/main/java/rs117/hd/utils/buffer/GLTextureBuffer.java +++ b/src/main/java/rs117/hd/utils/buffer/GLTextureBuffer.java @@ -13,7 +13,6 @@ public class GLTextureBuffer extends GLBuffer { @Getter private int texId; - private final int texelSize; private final int internalFormat; public GLTextureBuffer(String name, int usage) { @@ -23,13 +22,7 @@ public GLTextureBuffer(String name, int usage) { public GLTextureBuffer(String name, int usage, int storageFlags) { super(name, GL_TEXTURE_BUFFER, usage, storageFlags); - if(isRGBASupported()) { - texelSize = 4; - internalFormat = GL_RGBA32I; - } else { - texelSize = 3; - internalFormat = GL_RGB32I; - } + internalFormat = isRGBASupported() ? GL_RGBA32I : GL_RGB32I; } @Override @@ -39,17 +32,13 @@ public GLTextureBuffer initialize(long initialCapacity) { texId = glGenTextures(); glBindTexture(target, texId); glTexBuffer(target, internalFormat, id); - glBindTexture(target, 0); + return this; } @Override public boolean ensureCapacity(long byteOffset, long numBytes) { - // Ensure offset and size are aligned to texels - byteOffset = alignToTexel(byteOffset); - numBytes = alignToTexel(numBytes); - int oldId = id; final boolean resized = super.ensureCapacity(byteOffset, numBytes); @@ -62,15 +51,6 @@ public boolean ensureCapacity(long byteOffset, long numBytes) { return resized; } - private long alignToTexel(long numBytes) { - final int texelBytes = texelSize * 4; - final long remainder = numBytes % texelBytes; - if (remainder == 0) - return numBytes; - - return numBytes + (texelBytes - remainder); - } - @Override public void destroy() { if (texId != 0) @@ -79,4 +59,4 @@ public void destroy() { super.destroy(); } -} +} \ No newline at end of file From 89c0d04160b32cfcbd15b74a246537a0ef5dd4c3 Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Sun, 10 May 2026 04:26:51 +0100 Subject: [PATCH 5/9] Implemented ModelData Both static & dynamic models can now sample data specific to that model --- .../hd/opengl/shader/SceneShaderProgram.java | 4 + .../hd/opengl/shader/ShadowShaderProgram.java | 5 + .../hd/renderer/zone/DynamicModelVAO.java | 45 +++++--- .../renderer/zone/ModelStreamingManager.java | 16 +++ .../rs117/hd/renderer/zone/SceneUploader.java | 100 ++++++++++++------ .../hd/renderer/zone/VertexWriteCache.java | 10 +- .../java/rs117/hd/renderer/zone/Zone.java | 32 +++++- .../rs117/hd/renderer/zone/ZoneRenderer.java | 1 + .../rs117/hd/renderer/zone/ZoneUploadJob.java | 12 ++- src/main/resources/rs117/hd/scene_vert.glsl | 9 ++ .../rs117/hd/uniforms/model_data.glsl | 25 +++++ .../rs117/hd/uniforms/texture_faces.glsl | 9 +- 12 files changed, 205 insertions(+), 63 deletions(-) create mode 100644 src/main/resources/rs117/hd/uniforms/model_data.glsl diff --git a/src/main/java/rs117/hd/opengl/shader/SceneShaderProgram.java b/src/main/java/rs117/hd/opengl/shader/SceneShaderProgram.java index 1c75140e78..cb2275a09a 100644 --- a/src/main/java/rs117/hd/opengl/shader/SceneShaderProgram.java +++ b/src/main/java/rs117/hd/opengl/shader/SceneShaderProgram.java @@ -4,6 +4,7 @@ import static rs117.hd.HdPlugin.TEXTURE_UNIT_GAME; import static rs117.hd.HdPlugin.TEXTURE_UNIT_SHADOW_MAP; import static rs117.hd.HdPlugin.TEXTURE_UNIT_TILED_LIGHTING_MAP; +import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_MODEL_DATA; import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_TEXTURED_FACES; public class SceneShaderProgram extends ShaderProgram { @@ -11,6 +12,7 @@ public class SceneShaderProgram extends ShaderProgram { protected final UniformTexture uniShadowMap = addUniformTexture("shadowMap"); protected final UniformTexture uniTiledLightingTextureArray = addUniformTexture("tiledLightingArray"); protected final UniformTexture uniTextureFaces = addUniformTexture("textureFaces"); + protected final UniformTexture uniModelData = addUniformTexture("modelData"); public SceneShaderProgram() { super(t -> t @@ -25,12 +27,14 @@ protected void initialize() { uniShadowMap.set(TEXTURE_UNIT_SHADOW_MAP); uniTiledLightingTextureArray.set(TEXTURE_UNIT_TILED_LIGHTING_MAP); uniTextureFaces.set(TEXTURE_UNIT_TEXTURED_FACES); + uniModelData.set(TEXTURE_UNIT_MODEL_DATA); } public static class Legacy extends SceneShaderProgram { Legacy() { shaderTemplate.add(GL_GEOMETRY_SHADER, "scene_geom.glsl"); uniTextureFaces.ignoreMissing = true; + uniModelData.ignoreMissing = true; } } } diff --git a/src/main/java/rs117/hd/opengl/shader/ShadowShaderProgram.java b/src/main/java/rs117/hd/opengl/shader/ShadowShaderProgram.java index 1663800a11..bdd8e42197 100644 --- a/src/main/java/rs117/hd/opengl/shader/ShadowShaderProgram.java +++ b/src/main/java/rs117/hd/opengl/shader/ShadowShaderProgram.java @@ -5,11 +5,13 @@ import static org.lwjgl.opengl.GL33C.*; import static rs117.hd.HdPlugin.TEXTURE_UNIT_GAME; +import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_MODEL_DATA; import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_TEXTURED_FACES; public abstract class ShadowShaderProgram extends ShaderProgram { protected final UniformTexture uniTextureArray = addUniformTexture("textureArray"); protected final UniformTexture uniTextureFaces = addUniformTexture("textureFaces"); + protected final UniformTexture uniModelData = addUniformTexture("modelData"); protected ShadowMode mode; @@ -23,6 +25,7 @@ public abstract class ShadowShaderProgram extends ShaderProgram { protected void initialize() { uniTextureArray.set(TEXTURE_UNIT_GAME); uniTextureFaces.set(TEXTURE_UNIT_TEXTURED_FACES); + uniModelData.set(TEXTURE_UNIT_MODEL_DATA); } @Override @@ -47,6 +50,8 @@ public static class Legacy extends ShadowShaderProgram { public Legacy setMode(ShadowMode mode) { this.mode = mode; uniTextureArray.ignoreMissing = mode != ShadowMode.DETAILED; + uniTextureFaces.ignoreMissing = true; + uniModelData.ignoreMissing = true; return this; } diff --git a/src/main/java/rs117/hd/renderer/zone/DynamicModelVAO.java b/src/main/java/rs117/hd/renderer/zone/DynamicModelVAO.java index 5e1dccb3c0..d504e27e16 100644 --- a/src/main/java/rs117/hd/renderer/zone/DynamicModelVAO.java +++ b/src/main/java/rs117/hd/renderer/zone/DynamicModelVAO.java @@ -17,6 +17,7 @@ import static rs117.hd.HdPlugin.NVIDIA_GPU; import static rs117.hd.HdPlugin.SUPPORTS_INDIRECT_DRAW; import static rs117.hd.HdPlugin.SUPPORTS_STORAGE_BUFFERS; +import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_MODEL_DATA; import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_TEXTURED_FACES; import static rs117.hd.utils.MathUtils.*; import static rs117.hd.utils.buffer.GLBuffer.STORAGE_IMMUTABLE; @@ -44,13 +45,15 @@ public class DynamicModelVAO implements Destructible { private final GLBuffer vboRender; private final GLBuffer vboStaging; - private final GLTextureBuffer tbo; + private final GLTextureBuffer tboF; + private final GLTextureBuffer tboM; private final ArrayDeque usedViews = new ArrayDeque<>(); private final ArrayDeque freeViews = new ArrayDeque<>(); private final GLMappedBufferIntWriter vboWriter; - private final GLMappedBufferIntWriter tboWriter; + private final GLMappedBufferIntWriter tboFWriter; + private final GLMappedBufferIntWriter tboMWriter; private boolean isMapped = false; private int[] drawOffsets = new int[16]; @@ -81,15 +84,18 @@ public class DynamicModelVAO implements Destructible { } this.vboWriter = new GLMappedBufferIntWriter(this.vboStaging); - this.tbo = new GLTextureBuffer("VAO::TBO::" + name, GL_STREAM_DRAW, STORAGE_PERSISTENT | STORAGE_IMMUTABLE | STORAGE_WRITE); - this.tboWriter = new GLMappedBufferIntWriter(this.tbo); + this.tboF = new GLTextureBuffer("VAO::TexturedFaces::" + name, GL_STREAM_DRAW, STORAGE_PERSISTENT | STORAGE_IMMUTABLE | STORAGE_WRITE); + this.tboM = new GLTextureBuffer("VAO::ModelData::" + name, GL_STREAM_DRAW, STORAGE_PERSISTENT | STORAGE_IMMUTABLE | STORAGE_WRITE); + this.tboFWriter = new GLMappedBufferIntWriter(this.tboF); + this.tboMWriter = new GLMappedBufferIntWriter(this.tboM); } public boolean hasStagingBuffer() { return vboRender != vboStaging; } void initialize() { vao = glGenVertexArrays(); - tbo.initialize(INITIAL_SIZE); + tboF.initialize(INITIAL_SIZE); + tboM.initialize(INITIAL_SIZE); vboRender.initialize(INITIAL_SIZE); if (vboRender != vboStaging) vboStaging.initialize(INITIAL_SIZE); @@ -143,7 +149,8 @@ void bindRenderVAO() { void map() { vboWriter.map(false); - tboWriter.map(false); + tboFWriter.map(false); + tboMWriter.map(false); reset(); isMapped = true; @@ -152,7 +159,8 @@ void map() { synchronized void unmap(boolean coalesce) { final int renderVBOId = vboRender.id; long vboWrittenBytes = vboWriter.flush(); - tboWriter.flush(); + tboFWriter.flush(); + tboMWriter.flush(); if (drawRangeCount > 0) { mergeRanges(); @@ -192,10 +200,12 @@ synchronized void unmap(boolean coalesce) { @Override public void destroy() { vboWriter.destroy(); - tboWriter.destroy(); + tboFWriter.destroy(); + tboMWriter.destroy(); vboRender.destroy(); vboStaging.destroy(); - tbo.destroy(); + tboF.destroy(); + tboM.destroy(); if (vao != 0) glDeleteVertexArrays(vao); @@ -231,9 +241,11 @@ public synchronized View beginDraw(int drawIdx, int faceCount) { if (view == null) view = new View(); view.vbo = vboWriter.reserve(faceCount * 3 * VERT_SIZE_INTS); - view.tbo = tboWriter.reserve(faceCount * 9); + view.tboF = tboFWriter.reserve(faceCount * 9); + view.tboM = tboMWriter.reserve(4); view.vao = vao; - view.tboTexId = tbo.getTexId(); + view.tboFId = tboF.getTexId(); + view.tboMId = tboM.getTexId(); view.drawIdx = drawIdx; return view; @@ -252,7 +264,7 @@ private synchronized void endDraw(View view) { // Clear ReservedViews before returning to pool view.vbo = null; - view.tbo = null; + view.tboF = null; usedViews.add(view); } @@ -280,7 +292,8 @@ void draw(CommandBuffer cmd) { return; cmd.BindVertexArray(vao); - cmd.BindTextureUnit(GL_TEXTURE_BUFFER, tbo.getTexId(), TEXTURE_UNIT_TEXTURED_FACES); + cmd.BindTextureUnit(GL_TEXTURE_BUFFER, tboF.getTexId(), TEXTURE_UNIT_TEXTURED_FACES); + cmd.BindTextureUnit(GL_TEXTURE_BUFFER, tboM.getTexId(), TEXTURE_UNIT_MODEL_DATA); if (drawRangeCount == 1) { if (GL_CAPS.OpenGL40 && SUPPORTS_INDIRECT_DRAW) { @@ -307,9 +320,11 @@ void reset() { public final class View { public ReservedView vbo; - public ReservedView tbo; + public ReservedView tboF; + public ReservedView tboM; public int vao; - public int tboTexId; + public int tboFId; + public int tboMId; private int drawIdx; public int getStartOffset() { diff --git a/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java b/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java index 7b9a941558..54533d0dec 100644 --- a/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java +++ b/src/main/java/rs117/hd/renderer/zone/ModelStreamingManager.java @@ -328,12 +328,15 @@ private void uploadTempModel( (!sceneManager.isRoot(ctx) || zone.inShadowFrustum) ) { final DynamicModelVAO.View shadowView = ctx.beginDraw(VAO_SHADOW, culledFaces.length); + int shadowModelIdx = SceneUploader.writeModelData(shadowView.tboM.getBuffer(), x, y, z, m, modelOverride); sceneUploader.uploadTempModel( culledFaces, m, modelOverride, preOrientation, orientation, + shadowModelIdx, + shadowModelIdx, true, shadowView, shadowView @@ -351,12 +354,17 @@ private void uploadTempModel( final DynamicModelVAO.View opaqueView = ctx.beginDraw(isPlayer ? VAO_PLAYER : VAO_OPAQUE, drawIndex, opaqueFaceCount); final DynamicModelVAO.View alphaView = alphaFaceCount > 0 ? ctx.beginDraw(VAO_ALPHA, alphaFaceCount) : opaqueView; + final int opaqueModelIdx = SceneUploader.writeModelData(opaqueView.tboM.getBuffer(), x, y, z, m, modelOverride); + final int alphaModelIdx = alphaFaceCount > 0 ? SceneUploader.writeModelData(alphaView.tboM.getBuffer(), x, y, z, m, modelOverride) : opaqueModelIdx; + sceneUploader.uploadTempModel( visibleFaces, m, modelOverride, preOrientation, orientation, + opaqueModelIdx, + alphaModelIdx, isSquashed, opaqueView, alphaView @@ -590,12 +598,15 @@ private void uploadDynamicModel( (!sceneManager.isRoot(ctx) || zone.inShadowFrustum) ) { final DynamicModelVAO.View shadowView = ctx.beginDraw(VAO_SHADOW, culledFaces.length); + final int shadowModelIdx = SceneUploader.writeModelData(shadowView.tboM.getBuffer(), x, y, z, m, modelOverride); sceneUploader.uploadTempModel( culledFaces, m, modelOverride, preOrientation, orient, + shadowModelIdx, + shadowModelIdx, true, shadowView, shadowView @@ -610,12 +621,17 @@ private void uploadDynamicModel( final DynamicModelVAO.View opaqueView = ctx.beginDraw(VAO_OPAQUE, drawIndex, opaqueFaceCount); final DynamicModelVAO.View alphaView = alphaFaceCount > 0 ? ctx.beginDraw(VAO_ALPHA, alphaFaceCount) : opaqueView; + final int opaqueModelIdx = SceneUploader.writeModelData(opaqueView.tboM.getBuffer(), x, y, z, m, modelOverride); + final int alphaModelIdx = alphaFaceCount > 0 ? SceneUploader.writeModelData(alphaView.tboM.getBuffer(), x, y, z, m, modelOverride) : opaqueModelIdx; + sceneUploader.uploadTempModel( visibleFaces, m, modelOverride, preOrientation, orient, + opaqueModelIdx, + alphaModelIdx, isSquashed, opaqueView, alphaView diff --git a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java index 160bab014e..d830fef43f 100644 --- a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java +++ b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java @@ -24,6 +24,7 @@ */ package rs117.hd.renderer.zone; +import java.nio.IntBuffer; import java.util.HashSet; import java.util.Set; import javax.inject.Inject; @@ -196,7 +197,8 @@ public void uploadZone(ZoneSceneContext ctx, Zone zone, int mzx, int mzz) throws var vb = zone.vboO != null ? new GpuIntBuffer(zone.vboO.mapped()) : null; var ab = zone.vboA != null ? new GpuIntBuffer(zone.vboA.mapped()) : null; var fb = zone.tboF != null ? new GpuIntBuffer(zone.tboF.mapped()) : null; - assert fb != null; + var md = zone.tboF != null ? new GpuIntBuffer(zone.tboM.mapped()) : null; + assert fb != null && md != null; roofIds.clear(); for (int level = 0; level <= 3; ++level) { @@ -217,13 +219,13 @@ public void uploadZone(ZoneSceneContext ctx, Zone zone, int mzx, int mzz) throws this.level = z; if (z == 0) { - uploadZoneLevel(ctx, zone, mzx, mzz, 0, false, roofIds, vb, ab, fb); - uploadZoneLevel(ctx, zone, mzx, mzz, 0, true, roofIds, vb, ab, fb); - uploadZoneLevel(ctx, zone, mzx, mzz, 1, true, roofIds, vb, ab, fb); - uploadZoneLevel(ctx, zone, mzx, mzz, 2, true, roofIds, vb, ab, fb); - uploadZoneLevel(ctx, zone, mzx, mzz, 3, true, roofIds, vb, ab, fb); + uploadZoneLevel(ctx, zone, mzx, mzz, 0, false, roofIds, vb, ab, fb, md); + uploadZoneLevel(ctx, zone, mzx, mzz, 0, true, roofIds, vb, ab, fb, md); + uploadZoneLevel(ctx, zone, mzx, mzz, 1, true, roofIds, vb, ab, fb, md); + uploadZoneLevel(ctx, zone, mzx, mzz, 2, true, roofIds, vb, ab, fb, md); + uploadZoneLevel(ctx, zone, mzx, mzz, 3, true, roofIds, vb, ab, fb, md); } else { - uploadZoneLevel(ctx, zone, mzx, mzz, z, false, roofIds, vb, ab, fb); + uploadZoneLevel(ctx, zone, mzx, mzz, z, false, roofIds, vb, ab, fb, md); } if (vb != null) { @@ -249,7 +251,8 @@ private void uploadZoneLevel( Set roofIds, GpuIntBuffer vb, GpuIntBuffer ab, - GpuIntBuffer fb + GpuIntBuffer fb, + GpuIntBuffer md ) throws InterruptedException { int ridx = 0; @@ -257,7 +260,7 @@ private void uploadZoneLevel( for (int id : roofIds) { int pos = vb != null ? vb.position() : 0; - uploadZoneLevelRoof(ctx, zone, mzx, mzz, level, id, visbelow, vb, ab, fb); + uploadZoneLevelRoof(ctx, zone, mzx, mzz, level, id, visbelow, vb, ab, fb, md); int endpos = vb != null ? vb.position() : 0; @@ -270,7 +273,7 @@ private void uploadZoneLevel( } // upload everything else - uploadZoneLevelRoof(ctx, zone, mzx, mzz, level, 0, visbelow, vb, ab, fb); + uploadZoneLevelRoof(ctx, zone, mzx, mzz, level, 0, visbelow, vb, ab, fb, md); } private void uploadZoneLevelRoof( @@ -283,7 +286,8 @@ private void uploadZoneLevelRoof( boolean visbelow, GpuIntBuffer vb, GpuIntBuffer ab, - GpuIntBuffer fb + GpuIntBuffer fb, + GpuIntBuffer md ) throws InterruptedException { this.basex = (mzx - (ctx.sceneOffset >> 3)) << 10; this.basez = (mzz - (ctx.sceneOffset >> 3)) << 10; @@ -317,7 +321,7 @@ private void uploadZoneLevelRoof( this.rid = rid; if (onBeforeProcessTile != null) onBeforeProcessTile.invoke(t, false); - uploadZoneTile(ctx, zone, t, false, false, vb, ab, fb); + uploadZoneTile(ctx, zone, t, false, false, vb, ab, fb, md); } } } @@ -344,7 +348,7 @@ private void uploadZoneWater( if (t != null) { if (onBeforeProcessTile != null) onBeforeProcessTile.invoke(t, false); - uploadZoneTile(ctx, zone, t, false, true, vb, null, fb); + uploadZoneTile(ctx, zone, t, false, true, vb, null, fb, null); } } } @@ -464,7 +468,8 @@ private void uploadZoneTile( boolean onlyWaterSurface, GpuIntBuffer vertexBuffer, GpuIntBuffer alphaBuffer, - GpuIntBuffer textureBuffer + GpuIntBuffer textureBuffer, + GpuIntBuffer modelBuffer ) { var tilePoint = t.getSceneLocation(); int tileExX = tilePoint.getX() + ctx.sceneOffset; @@ -496,11 +501,11 @@ private void uploadZoneTile( uploadTileModel(ctx, t, model, onlyWaterSurface, tileExX, tileExY, tileZ, basex, basez, vertexBuffer, textureBuffer); if (!onlyWaterSurface) - uploadZoneTileRenderables(ctx, zone, t, vertexBuffer, alphaBuffer, textureBuffer); + uploadZoneTileRenderables(ctx, zone, t, vertexBuffer, alphaBuffer, textureBuffer, modelBuffer); Tile bridge = t.getBridge(); if (bridge != null) - uploadZoneTile(ctx, zone, bridge, true, onlyWaterSurface, vertexBuffer, alphaBuffer, textureBuffer); + uploadZoneTile(ctx, zone, bridge, true, onlyWaterSurface, vertexBuffer, alphaBuffer, textureBuffer, modelBuffer); } private void uploadZoneTileRenderables( @@ -509,7 +514,8 @@ private void uploadZoneTileRenderables( Tile t, GpuIntBuffer vertexBuffer, GpuIntBuffer alphaBuffer, - GpuIntBuffer textureBuffer + GpuIntBuffer textureBuffer, + GpuIntBuffer modelBuffer ) { WallObject wallObject = t.getWallObject(); if (wallObject != null && renderCallbackManager.drawObject(ctx.scene, wallObject)) { @@ -533,7 +539,8 @@ private void uploadZoneTileRenderables( wallObject.getId(), vertexBuffer, alphaBuffer, - textureBuffer + textureBuffer, + modelBuffer ); Renderable renderable2 = wallObject.getRenderable2(); @@ -555,7 +562,8 @@ private void uploadZoneTileRenderables( wallObject.getId(), vertexBuffer, alphaBuffer, - textureBuffer + textureBuffer, + modelBuffer ); } @@ -582,7 +590,8 @@ private void uploadZoneTileRenderables( decorativeObject.getId(), vertexBuffer, alphaBuffer, - textureBuffer + textureBuffer, + modelBuffer ); Renderable renderable2 = decorativeObject.getRenderable2(); @@ -604,7 +613,8 @@ private void uploadZoneTileRenderables( decorativeObject.getId(), vertexBuffer, alphaBuffer, - textureBuffer + textureBuffer, + modelBuffer ); } @@ -626,7 +636,8 @@ private void uploadZoneTileRenderables( groundObject.getId(), vertexBuffer, alphaBuffer, - textureBuffer + textureBuffer, + modelBuffer ); } @@ -659,7 +670,8 @@ private void uploadZoneTileRenderables( gameObject.getId(), vertexBuffer, alphaBuffer, - textureBuffer + textureBuffer, + modelBuffer ); } } @@ -688,6 +700,7 @@ private void estimateRenderableSize(Zone z, Renderable r, ModelOverride modelOve z.sizeA += faceCount; } z.sizeF += faceCount; + z.sizeM++; } private void uploadZoneRenderable( @@ -708,7 +721,8 @@ private void uploadZoneRenderable( int id, GpuIntBuffer opaqueBuffer, GpuIntBuffer alphaBuffer, - GpuIntBuffer textureBuffer + GpuIntBuffer textureBuffer, + GpuIntBuffer modelBuffer ) { Model model; if (r instanceof Model) { @@ -742,7 +756,8 @@ private void uploadZoneRenderable( x - basex, y, z - basez, opaqueBuffer, alphaBuffer, - textureBuffer + textureBuffer, + modelBuffer ); } catch (Throwable ex) { log.warn( @@ -777,6 +792,7 @@ private void uploadZoneRenderable( materialManager, zone.glVaoA, zone.tboF.getTexId(), + zone.tboM.getTexId(), model, modelOverride, alphaStart, alphaEnd, x - basex, y, z - basez, lx, lz, ux, uz, @@ -1341,6 +1357,16 @@ private void uploadTileModel( } } + public static int writeModelData(IntBuffer modelBuffer, int x, int y, int z, Model model, ModelOverride override) { + int modelIdx = modelBuffer.position() / 4; + modelBuffer + .put(x) + .put(y) + .put(z) + .put(model.getModelHeight()); + return modelIdx + 1; + } + // scene upload private int uploadStaticModel( ZoneSceneContext ctx, @@ -1352,7 +1378,8 @@ private int uploadStaticModel( int x, int y, int z, GpuIntBuffer opaqueBuffer, GpuIntBuffer alphaBuffer, - GpuIntBuffer textureBuffer + GpuIntBuffer textureBuffer, + GpuIntBuffer modelBuffer ) { if (writeCache == null) writeCache = new VertexWriteCache.Collection(); @@ -1442,6 +1469,8 @@ private int uploadStaticModel( final Material baseMaterial = modelOverride.baseMaterial; final Material textureMaterial = modelOverride.textureMaterial; + final int modelIdx = writeModelData(modelBuffer.getBuffer(), x, y, z, model, modelOverride); + int len = 0; for (int face = 0; face < faceCount; ++face) { int color1 = color1s[face]; @@ -1683,21 +1712,21 @@ private int uploadStaticModel( vx1, vy1, vz1, faceUVs[0], faceUVs[1], faceUVs[2], modelNormals[0], modelNormals[1], modelNormals[2], - texturedFaceIdx + texturedFaceIdx, modelIdx ); vb.putStaticVertex( vx2, vy2, vz2, faceUVs[4], faceUVs[5], faceUVs[6], modelNormals[3], modelNormals[4], modelNormals[5], - texturedFaceIdx + texturedFaceIdx, modelIdx ); vb.putStaticVertex( vx3, vy3, vz3, faceUVs[8], faceUVs[9], faceUVs[10], modelNormals[6], modelNormals[7], modelNormals[8], - texturedFaceIdx + texturedFaceIdx, modelIdx ); len += 3; } @@ -1932,6 +1961,8 @@ public void uploadTempModel( ModelOverride modelOverride, int preOrientation, int orientation, + int opaqueModelIdx, + int alphaModelIdx, boolean isShadow, DynamicModelVAO.View opaqueView, DynamicModelVAO.View alphaView @@ -1941,8 +1972,8 @@ public void uploadTempModel( writeCache.setOutputBuffers( opaqueView.vbo.getBuffer(), alphaView.vbo.getBuffer(), - opaqueView.tbo.getBuffer(), - alphaView.tbo.getBuffer() + opaqueView.tboF.getBuffer(), + alphaView.tboF.getBuffer() ); final int[] indices1 = model.getFaceIndices1(); @@ -2095,6 +2126,7 @@ else if (color3 == -1) color2 |= packedAlphaBiasHsl; color3 |= packedAlphaBiasHsl; + final int modelIdx = hasAlpha ? alphaModelIdx : opaqueModelIdx; final int texturedFaceIdx = tb.putFace( color1, color2, color3, materialData, materialData, materialData, @@ -2105,19 +2137,19 @@ else if (color3 == -1) modelLocalI[vertexOffsetA], modelLocalI[vertexOffsetA + 1], modelLocalI[vertexOffsetA + 2], faceUVs[0], faceUVs[1], faceUVs[2], faceNormals[0], faceNormals[1], faceNormals[2], - texturedFaceIdx + texturedFaceIdx, modelIdx ); vb.putVertex( modelLocalI[vertexOffsetB], modelLocalI[vertexOffsetB + 1], modelLocalI[vertexOffsetB + 2], faceUVs[4], faceUVs[5], faceUVs[6], faceNormals[3], faceNormals[4], faceNormals[5], - texturedFaceIdx + texturedFaceIdx, modelIdx ); vb.putVertex( modelLocalI[vertexOffsetC], modelLocalI[vertexOffsetC + 1], modelLocalI[vertexOffsetC + 2], faceUVs[8], faceUVs[9], faceUVs[10], faceNormals[6], faceNormals[7], faceNormals[8], - texturedFaceIdx + texturedFaceIdx, modelIdx ); } diff --git a/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java b/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java index 7807942912..3501b5126d 100644 --- a/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java +++ b/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java @@ -71,7 +71,7 @@ public void putVertex( int x, int y, int z, float u, float v, float w, int nx, int ny, int nz, - int textureFaceIdx + int textureFaceIdx, int modelIdx ) { if (stagingPosition + 8 > stagingBuffer.length) flushAndGrow(); @@ -83,9 +83,9 @@ public void putVertex( stagingBuffer[stagingPosition + 1] = y; stagingBuffer[stagingPosition + 2] = z; stagingBuffer[stagingPosition + 3] = float16(v) << 16 | float16(u); - stagingBuffer[stagingPosition + 4] = float16(w); + stagingBuffer[stagingPosition + 4] = modelIdx << 16 | float16(w); stagingBuffer[stagingPosition + 5] = (ny & 0xFFFF) << 16 | nx & 0xFFFF; - stagingBuffer[stagingPosition + 6] = nz & 0xFFFF; + stagingBuffer[stagingPosition + 6] = (modelIdx & 0xFFFF) << 16 | nz & 0xFFFF; stagingBuffer[stagingPosition + 7] = textureFaceIdx; this.stagingPosition += 8; @@ -95,7 +95,7 @@ public void putStaticVertex( int x, int y, int z, float u, float v, float w, int nx, int ny, int nz, - int textureFaceIdx + int textureFaceIdx, int modelIdx ) { if (stagingPosition + 7 > stagingBuffer.length) flushAndGrow(); @@ -109,7 +109,7 @@ public void putStaticVertex( stagingBuffer[stagingPosition + 3] = float16(w); // Unnormalized normals, assumed to be within short max stagingBuffer[stagingPosition + 4] = (ny & 0xFFFF) << 16 | nx & 0xFFFF; - stagingBuffer[stagingPosition + 5] = nz & 0xFFFF; + stagingBuffer[stagingPosition + 5] = (modelIdx & 0xFFFF) << 16 | nz & 0xFFFF; stagingBuffer[stagingPosition + 6] = textureFaceIdx; this.stagingPosition += 7; diff --git a/src/main/java/rs117/hd/renderer/zone/Zone.java b/src/main/java/rs117/hd/renderer/zone/Zone.java index 97f8004142..be926450cd 100644 --- a/src/main/java/rs117/hd/renderer/zone/Zone.java +++ b/src/main/java/rs117/hd/renderer/zone/Zone.java @@ -32,6 +32,7 @@ import static rs117.hd.HdPlugin.GL_CAPS; import static rs117.hd.HdPlugin.SUPPORTS_INDIRECT_DRAW; import static rs117.hd.HdPlugin.checkGLErrors; +import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_MODEL_DATA; import static rs117.hd.renderer.zone.ZoneRenderer.TEXTURE_UNIT_TEXTURED_FACES; import static rs117.hd.renderer.zone.ZoneRenderer.eboAlpha; import static rs117.hd.utils.MathUtils.*; @@ -53,6 +54,10 @@ public class Zone implements Destructible { // terrainData ivec3 public static final int TEXTURE_SIZE = 36; + // position vec3 + // height float + public static final int MODEL_DATA_SIZE = 16; + // Metadata format // worldViewIndex int int // sceneOffset int vec2(x, y) @@ -67,10 +72,10 @@ public class Zone implements Destructible { public int glVaoA; public int bufLenA; - public int sizeO, sizeA, sizeF; + public int sizeO, sizeA, sizeF, sizeM; @Nullable public GLBuffer vboO, vboA, vboM; - public GLTextureBuffer tboF; + public GLTextureBuffer tboF, tboM; public boolean initialized; // whether the zone vao and vbos are ready public boolean cull; // whether the zone is queued for deletion @@ -97,7 +102,7 @@ public class Zone implements Destructible { final List alphaModels = new ArrayList<>(0); final ConcurrentLinkedQueue pendingModelJobs = new ConcurrentLinkedQueue<>(); - public void initialize(GLBuffer o, GLBuffer a, GLTextureBuffer f) { + public void initialize(GLBuffer o, GLBuffer a, GLTextureBuffer f, GLTextureBuffer m) { assert glVao == 0; assert glVaoA == 0; if (o == null && a == null || f == null) @@ -119,6 +124,7 @@ public void initialize(GLBuffer o, GLBuffer a, GLTextureBuffer f) { } tboF = f; + tboM = m; } public static void freeZones(@Nullable Zone[][] zones) { @@ -160,6 +166,11 @@ public void destroy() { tboF = null; } + if (tboM != null) { + tboM.destroy(); + tboM = null; + } + if (glVao != 0) { glDeleteVertexArrays(glVao); glVao = 0; @@ -218,6 +229,8 @@ public void unmap() { vboA.unmap(); if (tboF != null) tboF.unmap(); + if(tboM != null) + tboM.unmap(); if (vboO != null) { this.bufLen = vboO.mapped().byteView().position() / VERT_SIZE; @@ -372,6 +385,7 @@ void renderOpaque(CommandBuffer cmd, WorldViewContext ctx, boolean roofShadows) lastDrawMode = STATIC_UNSORTED; lastVao = glVao; lastTboF = tboF.getTexId(); + lastTboM = tboM.getTexId(); flush(cmd); } @@ -386,6 +400,7 @@ void renderOpaqueLevel(CommandBuffer cmd, int level) { lastDrawMode = STATIC_UNSORTED; lastVao = glVao; lastTboF = tboF.getTexId(); + lastTboM = tboM.getTexId(); flush(cmd); } @@ -411,6 +426,7 @@ public static class AlphaModel { short rid; int vao; int tboF; + int tboM; byte level; byte lx, lz, ux, uz; // lower/upper zone coords byte zofx, zofz; // for temp alpha models, offset of source zone from target zone @@ -443,7 +459,8 @@ boolean isTemp() { void setView(DynamicModelVAO.View view) { vao = view.vao; - tboF = view.tboTexId; + tboF = view.tboFId; + tboM = view.tboMId; startpos = view.getStartOffset(); endpos = view.getEndOffset(); } @@ -456,6 +473,7 @@ void addAlphaModel( MaterialManager materialManager, int vao, int tboF, + int tboM, Model model, ModelOverride modelOverride, int startpos, @@ -481,6 +499,7 @@ void addAlphaModel( m.z = (short) z; m.vao = vao; m.tboF = tboF; + m.tboM = tboM; m.rid = (short) rid; m.level = (byte) level; if (lx > -1) { @@ -657,6 +676,7 @@ synchronized void postAlphaPass() { private static int lastDrawMode; private static int lastVao; private static int lastTboF; + private static int lastTboM; private static int lastzx, lastzz; private static final class AlphaSortPredicate implements ToIntFunction { @@ -747,6 +767,7 @@ void renderAlpha( if (lastDrawMode != drawMode || lastVao != m.vao || lastTboF != m.tboF || + lastTboM != m.tboM || lastzx != (zx - m.zofx) || lastzz != (zz - m.zofz) ) { @@ -754,6 +775,7 @@ void renderAlpha( lastDrawMode = drawMode; lastVao = m.vao; lastTboF = m.tboF; + lastTboM = m.tboM; lastzx = zx - m.zofx; lastzz = zz - m.zofz; } @@ -797,6 +819,7 @@ private void flush(CommandBuffer cmd) { long byteOffset = 4L * (eboAlphaOffset - vertexCount); cmd.BindVertexArray(lastVao, eboAlpha); cmd.BindTextureUnit(GL_TEXTURE_BUFFER, lastTboF, TEXTURE_UNIT_TEXTURED_FACES); + cmd.BindTextureUnit(GL_TEXTURE_BUFFER, lastTboM, TEXTURE_UNIT_MODEL_DATA); // The EBO & IDO is bound by in ZoneRenderer if (GL_CAPS.OpenGL40 && SUPPORTS_INDIRECT_DRAW) { cmd.DrawElementsIndirect(GL_TRIANGLES, vertexCount, (int) (byteOffset / 4L), ZoneRenderer.indirectDrawCmdsStaging); @@ -809,6 +832,7 @@ private void flush(CommandBuffer cmd) { convertForDraw(lastDrawMode == STATIC_UNSORTED ? VERT_SIZE : DynamicModelVAO.VERT_SIZE); cmd.BindVertexArray(lastVao); cmd.BindTextureUnit(GL_TEXTURE_BUFFER, lastTboF, TEXTURE_UNIT_TEXTURED_FACES); + cmd.BindTextureUnit(GL_TEXTURE_BUFFER, lastTboM, TEXTURE_UNIT_MODEL_DATA); if (drawIdx == 1) { if (GL_CAPS.OpenGL40 && SUPPORTS_INDIRECT_DRAW) { cmd.DrawArraysIndirect(GL_TRIANGLES, drawOff[0], drawEnd[0], ZoneRenderer.indirectDrawCmdsStaging); diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index be35d19b5c..c4daf422c1 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -91,6 +91,7 @@ public class ZoneRenderer implements Renderer { private static int TEXTURE_UNIT_COUNT = HdPlugin.TEXTURE_UNIT_COUNT; public static final int TEXTURE_UNIT_TEXTURED_FACES = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++; + public static final int TEXTURE_UNIT_MODEL_DATA = GL_TEXTURE0 + TEXTURE_UNIT_COUNT++; private static int UNIFORM_BLOCK_COUNT = HdPlugin.UNIFORM_BLOCK_COUNT; public static final int UNIFORM_BLOCK_WORLD_VIEWS = UNIFORM_BLOCK_COUNT++; diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneUploadJob.java b/src/main/java/rs117/hd/renderer/zone/ZoneUploadJob.java index e7f41ee7a0..1620e85452 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneUploadJob.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneUploadJob.java @@ -72,12 +72,20 @@ private void mapZoneVertexBuffers() { GLTextureBuffer f = null; sz = zone.sizeF * Zone.TEXTURE_SIZE; if (sz > 0) { - f = new GLTextureBuffer("Zone::TBO", GL_STATIC_DRAW); + f = new GLTextureBuffer("Zone::TexturedFaces", GL_STATIC_DRAW); f.initialize(sz); f.map(MAP_WRITE); } - zone.initialize(o, a, f); + GLTextureBuffer m = null; + sz = zone.sizeM * Zone.MODEL_DATA_SIZE; + if (sz > 0) { + m = new GLTextureBuffer("Zone::ModelData", GL_STATIC_DRAW); + m.initialize(sz); + m.map(MAP_WRITE); + } + + zone.initialize(o, a, f, m); zone.setMetadata(viewContext, sceneContext, x, z); } catch (Throwable ex) { log.warn( diff --git a/src/main/resources/rs117/hd/scene_vert.glsl b/src/main/resources/rs117/hd/scene_vert.glsl index f10bed621a..80d818afea 100644 --- a/src/main/resources/rs117/hd/scene_vert.glsl +++ b/src/main/resources/rs117/hd/scene_vert.glsl @@ -28,9 +28,11 @@ #include #include #include +#include #include #include +#include layout (location = 0) in vec3 vPosition; @@ -79,6 +81,13 @@ layout (location = 0) in vec3 vPosition; vec3 sceneOffset = vec3(vSceneBase.x, 0, vSceneBase.y); vec3 worldNormal = vNormal.xyz; vec3 worldPosition = sceneOffset + vPosition; + + int modelIdx = int(vNormal.w); + if(modelIdx > 0) { + //ModelData modelData = getModelData(modelIdx); + + } + if (vWorldViewId != -1) { mat4x3 worldViewProjection = mat4x3(getWorldViewProjection(vWorldViewId)); worldPosition = worldViewProjection * vec4(worldPosition, 1.0); diff --git a/src/main/resources/rs117/hd/uniforms/model_data.glsl b/src/main/resources/rs117/hd/uniforms/model_data.glsl new file mode 100644 index 0000000000..a5840f628d --- /dev/null +++ b/src/main/resources/rs117/hd/uniforms/model_data.glsl @@ -0,0 +1,25 @@ +#pragma once + +#include + +#define MODEL_DATA_SIZE 4 +#define PARSER_TARGET_BUFFER modelData + +uniform isamplerBuffer modelData; + +struct ModelData { + ivec3 position; + int height; +}; + + +BEGIN_BUFFER_PARSER(readModelData, ModelData) + READ_IVEC3(position) + READ_INT(height) +END_BUFFER_PARSER() + +ModelData getModelData(int modelIdx) { + return readModelData((modelIdx - 1) * MODEL_DATA_SIZE); +} + +#undef PARSER_TARGET_BUFFER \ No newline at end of file diff --git a/src/main/resources/rs117/hd/uniforms/texture_faces.glsl b/src/main/resources/rs117/hd/uniforms/texture_faces.glsl index ae2c64ca8c..c1e4141ac9 100644 --- a/src/main/resources/rs117/hd/uniforms/texture_faces.glsl +++ b/src/main/resources/rs117/hd/uniforms/texture_faces.glsl @@ -2,6 +2,9 @@ #include +#define FADE_DATA_SIZE 9 +#define PARSER_TARGET_BUFFER textureFaces + uniform isamplerBuffer textureFaces; struct FaceData { @@ -10,10 +13,10 @@ struct FaceData { ivec3 TerrainData; }; -#define PARSER_TARGET_BUFFER textureFaces - BEGIN_BUFFER_PARSER(getFaceData,FaceData) READ_IVEC3(AlphaBiasHsl) READ_IVEC3(MaterialData) READ_IVEC3(TerrainData) -END_BUFFER_PARSER() \ No newline at end of file +END_BUFFER_PARSER() + +#undef PARSER_TARGET_BUFFER \ No newline at end of file From 3685d93df342d3a418a0988e9fa460947285ef1e Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Mon, 11 May 2026 06:51:54 +0100 Subject: [PATCH 6/9] Ensure ModelDataSize is updated correctly --- src/main/java/rs117/hd/renderer/zone/SceneUploader.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java index d830fef43f..630908acd2 100644 --- a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java +++ b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java @@ -1357,13 +1357,16 @@ private void uploadTileModel( } } + public static final int MODEL_DATA_SIZE = 4; + public static int writeModelData(IntBuffer modelBuffer, int x, int y, int z, Model model, ModelOverride override) { - int modelIdx = modelBuffer.position() / 4; + int modelIdx = modelBuffer.position() / MODEL_DATA_SIZE; modelBuffer .put(x) .put(y) .put(z) .put(model.getModelHeight()); + assert modelBuffer.position() % MODEL_DATA_SIZE == 0; return modelIdx + 1; } From 105c55b415b5c78bc9620b49222a01ad677d276f Mon Sep 17 00:00:00 2001 From: Ruffled <105522716+RuffledPlume@users.noreply.github.com> Date: Mon, 11 May 2026 22:19:33 +0100 Subject: [PATCH 7/9] Write less data for models --- .../hd/renderer/zone/DynamicModelVAO.java | 2 +- .../rs117/hd/renderer/zone/SceneUploader.java | 12 ++------- .../hd/renderer/zone/VertexWriteCache.java | 22 ++++++++++++++-- .../rs117/hd/utils/buffer/GpuIntBuffer.java | 2 +- src/main/resources/rs117/hd/scene_vert.glsl | 26 ++++++++++++++----- src/main/resources/rs117/hd/shadow_vert.glsl | 22 ++++++++++++---- .../rs117/hd/uniforms/texture_faces.glsl | 23 +++++++++++++--- 7 files changed, 80 insertions(+), 29 deletions(-) diff --git a/src/main/java/rs117/hd/renderer/zone/DynamicModelVAO.java b/src/main/java/rs117/hd/renderer/zone/DynamicModelVAO.java index d504e27e16..cbdf3d66e0 100644 --- a/src/main/java/rs117/hd/renderer/zone/DynamicModelVAO.java +++ b/src/main/java/rs117/hd/renderer/zone/DynamicModelVAO.java @@ -241,7 +241,7 @@ public synchronized View beginDraw(int drawIdx, int faceCount) { if (view == null) view = new View(); view.vbo = vboWriter.reserve(faceCount * 3 * VERT_SIZE_INTS); - view.tboF = tboFWriter.reserve(faceCount * 9); + view.tboF = tboFWriter.reserve(faceCount * 4); view.tboM = tboMWriter.reserve(4); view.vao = vao; view.tboFId = tboF.getTexId(); diff --git a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java index 630908acd2..637b3b5616 100644 --- a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java +++ b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java @@ -1705,11 +1705,7 @@ private int uploadStaticModel( color2 |= packedAlphaBiasHsl; color3 |= packedAlphaBiasHsl; - final int texturedFaceIdx = tb.putFace( - color1, color2, color3, - materialData, materialData, materialData, - 0, 0, 0 - ); + final int texturedFaceIdx = tb.putModelFace(color1, color2, color3, materialData); vb.putStaticVertex( vx1, vy1, vz1, @@ -2130,11 +2126,7 @@ else if (color3 == -1) color3 |= packedAlphaBiasHsl; final int modelIdx = hasAlpha ? alphaModelIdx : opaqueModelIdx; - final int texturedFaceIdx = tb.putFace( - color1, color2, color3, - materialData, materialData, materialData, - 0, 0, 0 - ); + final int texturedFaceIdx = tb.putModelFace(color1, color2, color3, materialData); vb.putVertex( modelLocalI[vertexOffsetA], modelLocalI[vertexOffsetA + 1], modelLocalI[vertexOffsetA + 2], diff --git a/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java b/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java index 3501b5126d..6997310538 100644 --- a/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java +++ b/src/main/java/rs117/hd/renderer/zone/VertexWriteCache.java @@ -38,7 +38,7 @@ private void flushAndGrow() { stagingBuffer = new int[min(stagingBuffer.length * 2, maxCapacity)]; } - public int putFace( + public int putStaticFace( int alphaBiasHslA, int alphaBiasHslB, int alphaBiasHslC, int materialDataA, int materialDataB, int materialDataC, int terrainDataA, int terrainDataB, int terrainDataC @@ -64,7 +64,25 @@ public int putFace( this.stagingPosition += 9; - return textureFaceIdx; + return textureFaceIdx << 1; + } + + public int putModelFace(int alphaBiasHslA, int alphaBiasHslB, int alphaBiasHslC, int materialData) { + if (stagingPosition + 4 > stagingBuffer.length) + flushAndGrow(); + + final int textureFaceIdx = outputBuffer.position() + stagingPosition; + + final int[] stagingBuffer = this.stagingBuffer; + final int stagingPosition = this.stagingPosition; + + stagingBuffer[stagingPosition] = alphaBiasHslA; + stagingBuffer[stagingPosition + 1] = alphaBiasHslB; + stagingBuffer[stagingPosition + 2] = alphaBiasHslC; + stagingBuffer[stagingPosition + 3] = materialData; + + this.stagingPosition += 4; + return 1 | textureFaceIdx << 1; } public void putVertex( diff --git a/src/main/java/rs117/hd/utils/buffer/GpuIntBuffer.java b/src/main/java/rs117/hd/utils/buffer/GpuIntBuffer.java index ff0a066e33..c3a543c973 100644 --- a/src/main/java/rs117/hd/utils/buffer/GpuIntBuffer.java +++ b/src/main/java/rs117/hd/utils/buffer/GpuIntBuffer.java @@ -157,7 +157,7 @@ public static int putFace( buffer.put(terrainDataA); // TODO: Remove? buffer.put(terrainDataB); buffer.put(terrainDataC); - return textureFaceIdx; + return textureFaceIdx << 1; } public int position() { diff --git a/src/main/resources/rs117/hd/scene_vert.glsl b/src/main/resources/rs117/hd/scene_vert.glsl index 80d818afea..db45dfd6ab 100644 --- a/src/main/resources/rs117/hd/scene_vert.glsl +++ b/src/main/resources/rs117/hd/scene_vert.glsl @@ -39,7 +39,7 @@ layout (location = 0) in vec3 vPosition; #if ZONE_RENDERER layout (location = 1) in vec4 vUv; layout (location = 2) in vec4 vNormal; - layout (location = 3) in int vTextureFaceIdx; + layout (location = 3) in int vPackedTextureFace; layout (location = 6) in int vWorldViewId; layout (location = 7) in ivec2 vSceneBase; #else @@ -68,15 +68,27 @@ layout (location = 0) in vec3 vPosition; } OUT; void main() { - FaceData faceData = getFaceData(vTextureFaceIdx); - fAlphaBiasHsl = faceData.AlphaBiasHsl; - fMaterialData = faceData.MaterialData; - fTerrainData = faceData.TerrainData; fWorldViewId = vWorldViewId; int vertex = gl_VertexID % 3; - int alphaBiasHsl = faceData.AlphaBiasHsl[vertex]; - int materialData = faceData.MaterialData[vertex]; + int alphaBiasHsl; + int materialData; + + if(isModelFace(vPackedTextureFace)) { + ModelFaceData faceData = getModelFaceData(getFaceOffset(vPackedTextureFace)); + fAlphaBiasHsl = faceData.AlphaBiasHsl; + fMaterialData = ivec3(faceData.MaterialData); + fTerrainData = ivec3(0); + alphaBiasHsl = faceData.AlphaBiasHsl[vertex]; + materialData = faceData.MaterialData; + } else { + StaticFaceData faceData = getStaticFaceData(getFaceOffset(vPackedTextureFace)); + fAlphaBiasHsl = faceData.AlphaBiasHsl; + fMaterialData = faceData.MaterialData; + fTerrainData = faceData.TerrainData; + alphaBiasHsl = faceData.AlphaBiasHsl[vertex]; + materialData = faceData.MaterialData[vertex]; + } vec3 sceneOffset = vec3(vSceneBase.x, 0, vSceneBase.y); vec3 worldNormal = vNormal.xyz; diff --git a/src/main/resources/rs117/hd/shadow_vert.glsl b/src/main/resources/rs117/hd/shadow_vert.glsl index d6daf13056..b632a38ae5 100644 --- a/src/main/resources/rs117/hd/shadow_vert.glsl +++ b/src/main/resources/rs117/hd/shadow_vert.glsl @@ -36,7 +36,7 @@ layout (location = 0) in vec3 vPosition; #if ZONE_RENDERER layout (location = 1) in vec4 vUv; - layout (location = 3) in int vTextureFaceIdx; + layout (location = 3) in int vPackedTextureFace; layout (location = 6) in int vWorldViewId; layout (location = 7) in ivec2 vSceneBase; @@ -51,10 +51,22 @@ layout (location = 0) in vec3 vPosition; void main() { int vertex = gl_VertexID % 3; - FaceData faceData = getFaceData(vTextureFaceIdx); - int alphaBiasHsl = faceData.AlphaBiasHsl[vertex]; - int materialData = faceData.MaterialData[vertex]; - int terrainData = faceData.TerrainData[vertex]; + int alphaBiasHsl; + int materialData; + int terrainData; + int faceDataOffset; + + if(isModelFace(vPackedTextureFace)) { + ModelFaceData faceData = getModelFaceData(getFaceOffset(vPackedTextureFace)); + alphaBiasHsl = faceData.AlphaBiasHsl[vertex]; + materialData = faceData.MaterialData; + terrainData = 0; + } else { + StaticFaceData faceData = getStaticFaceData(getFaceOffset(vPackedTextureFace)); + alphaBiasHsl = faceData.AlphaBiasHsl[vertex]; + materialData = faceData.MaterialData[vertex]; + terrainData = faceData.TerrainData[vertex]; + } int waterTypeIndex = terrainData >> 3 & 0xFF; float opacity = 1 - (alphaBiasHsl >> 24 & 0xFF) / float(0xFF); diff --git a/src/main/resources/rs117/hd/uniforms/texture_faces.glsl b/src/main/resources/rs117/hd/uniforms/texture_faces.glsl index c1e4141ac9..8801f9ae9d 100644 --- a/src/main/resources/rs117/hd/uniforms/texture_faces.glsl +++ b/src/main/resources/rs117/hd/uniforms/texture_faces.glsl @@ -2,21 +2,38 @@ #include -#define FADE_DATA_SIZE 9 #define PARSER_TARGET_BUFFER textureFaces uniform isamplerBuffer textureFaces; -struct FaceData { +struct StaticFaceData { ivec3 AlphaBiasHsl; ivec3 MaterialData; ivec3 TerrainData; }; -BEGIN_BUFFER_PARSER(getFaceData,FaceData) +struct ModelFaceData { + ivec3 AlphaBiasHsl; + int MaterialData; +}; + +bool isModelFace(int packedFaceData) { + return (packedFaceData & 1) == 1; +} + +int getFaceOffset(int packedFaceData) { + return packedFaceData >> 1; +} + +BEGIN_BUFFER_PARSER(getStaticFaceData,StaticFaceData) READ_IVEC3(AlphaBiasHsl) READ_IVEC3(MaterialData) READ_IVEC3(TerrainData) END_BUFFER_PARSER() +BEGIN_BUFFER_PARSER(getModelFaceData,ModelFaceData) + READ_IVEC3(AlphaBiasHsl) + READ_INT(MaterialData) +END_BUFFER_PARSER() + #undef PARSER_TARGET_BUFFER \ No newline at end of file From 1001c7c5e428085644e0585542d873456d3457d6 Mon Sep 17 00:00:00 2001 From: John Schultz Date: Mon, 30 Mar 2026 22:08:11 -0400 Subject: [PATCH 8/9] First Pass --- .../rs117/hd/opengl/uniforms/UBOGlobal.java | 7 ++ .../rs117/hd/renderer/zone/ZoneRenderer.java | 7 ++ src/main/resources/rs117/hd/scene_vert.glsl | 8 ++ src/main/resources/rs117/hd/shadow_vert.glsl | 8 ++ .../resources/rs117/hd/uniforms/global.glsl | 7 ++ src/main/resources/rs117/hd/utils/wind.glsl | 98 +++++++++++++++++++ 6 files changed, 135 insertions(+) create mode 100644 src/main/resources/rs117/hd/utils/wind.glsl diff --git a/src/main/java/rs117/hd/opengl/uniforms/UBOGlobal.java b/src/main/java/rs117/hd/opengl/uniforms/UBOGlobal.java index 1343819ecc..087afeaaa5 100644 --- a/src/main/java/rs117/hd/opengl/uniforms/UBOGlobal.java +++ b/src/main/java/rs117/hd/opengl/uniforms/UBOGlobal.java @@ -63,4 +63,11 @@ public void initialize() { public Property lightningBrightness = addProperty(PropertyType.Float, "lightningBrightness"); public Property elapsedTime = addProperty(PropertyType.Float, "elapsedTime"); + + // Wind uniforms + public Property windDirectionX = addProperty(PropertyType.Float, "windDirectionX"); + public Property windDirectionZ = addProperty(PropertyType.Float, "windDirectionZ"); + public Property windStrength = addProperty(PropertyType.Float, "windStrength"); + public Property windCeiling = addProperty(PropertyType.Float, "windCeiling"); + public Property windOffset = addProperty(PropertyType.Float, "windOffset"); } diff --git a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java index c4daf422c1..3cac98b0c2 100644 --- a/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java +++ b/src/main/java/rs117/hd/renderer/zone/ZoneRenderer.java @@ -608,6 +608,13 @@ private void preSceneDrawTopLevel( plugin.uboGlobal.colorFilterFade.set(clamp(timeSinceChange / COLOR_FILTER_FADE_DURATION, 0, 1)); } + // Wind uniforms + plugin.uboGlobal.windDirectionX.set(cos(environmentManager.currentWindAngle)); + plugin.uboGlobal.windDirectionZ.set(sin(environmentManager.currentWindAngle)); + plugin.uboGlobal.windStrength.set(environmentManager.currentWindStrength); + plugin.uboGlobal.windCeiling.set(environmentManager.currentWindCeiling); + plugin.uboGlobal.windOffset.set(plugin.windOffset); + plugin.uboGlobal.upload(); // Reset buffers for the next frame diff --git a/src/main/resources/rs117/hd/scene_vert.glsl b/src/main/resources/rs117/hd/scene_vert.glsl index db45dfd6ab..ea998a6c22 100644 --- a/src/main/resources/rs117/hd/scene_vert.glsl +++ b/src/main/resources/rs117/hd/scene_vert.glsl @@ -34,6 +34,10 @@ #include #include +#if ZONE_RENDERER + #include +#endif + layout (location = 0) in vec3 vPosition; #if ZONE_RENDERER @@ -106,6 +110,10 @@ layout (location = 0) in vec3 vPosition; worldNormal = mat3(worldViewProjection) * worldNormal; } + #if WIND_DISPLACEMENT + worldPosition += computeZoneWindDisplacement(worldPosition, materialData, worldNormal); + #endif + OUT.position = worldPosition; OUT.uv = computeVertexUvs(materialData, worldPosition, vUv.xyz); OUT.normal = worldNormal; diff --git a/src/main/resources/rs117/hd/shadow_vert.glsl b/src/main/resources/rs117/hd/shadow_vert.glsl index b632a38ae5..4c43c46501 100644 --- a/src/main/resources/rs117/hd/shadow_vert.glsl +++ b/src/main/resources/rs117/hd/shadow_vert.glsl @@ -32,6 +32,10 @@ #include +#if ZONE_RENDERER + #include +#endif + layout (location = 0) in vec3 vPosition; #if ZONE_RENDERER @@ -113,6 +117,10 @@ layout (location = 0) in vec3 vPosition; worldPosition = worldViewProjection * vec4(worldPosition, 1.0);; } + #if WIND_DISPLACEMENT + worldPosition += computeZoneWindDisplacement(worldPosition, materialData, vec3(0, 1, 0)); + #endif + #if SHADOW_TRANSPARENCY fOpacity = opacity; #endif diff --git a/src/main/resources/rs117/hd/uniforms/global.glsl b/src/main/resources/rs117/hd/uniforms/global.glsl index a50e951d99..893015e0aa 100644 --- a/src/main/resources/rs117/hd/uniforms/global.glsl +++ b/src/main/resources/rs117/hd/uniforms/global.glsl @@ -52,4 +52,11 @@ layout(std140) uniform UBOGlobal { float lightningBrightness; float elapsedTime; + + // Wind uniforms + float windDirectionX; + float windDirectionZ; + float windStrength; + float windCeiling; + float windOffset; }; diff --git a/src/main/resources/rs117/hd/utils/wind.glsl b/src/main/resources/rs117/hd/utils/wind.glsl new file mode 100644 index 0000000000..d292d12d79 --- /dev/null +++ b/src/main/resources/rs117/hd/utils/wind.glsl @@ -0,0 +1,98 @@ +#pragma once + +#include +#include + +// Wind displacement modes (must match comp_common.glsl / WindDisplacement.java) +#define WIND_DISPLACEMENT_DISABLED 0 +#define WIND_DISPLACEMENT_OBJECT 1 +#define WIND_DISPLACEMENT_OBJECT_NO_GROUND_DISPLACEMENT 2 +#define WIND_DISPLACEMENT_VERTEX 3 +#define WIND_DISPLACEMENT_VERTEX_WITH_HEMISPHERE_BLEND 4 +#define WIND_DISPLACEMENT_VERTEX_JIGGLE 5 + +float getWindDisplacementMod(int materialData) { + const float modifiers[7] = float[7](0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 2.0); + int modifierIdx = (materialData >> MATERIAL_FLAG_WIND_MODIFIER) & 0x7; + return modifiers[modifierIdx]; +} + +// Compute per-vertex wind displacement for the zone renderer. +// This is a vertex-shader adaptation of the compute-shader wind in priority_render.glsl. +// Since we lack per-model info (model position, model height), we approximate: +// - Model-level noise: snap vertex world XZ to a coarse grid for coherent per-model motion +// - Height-based strength: derived from vertex world Y position relative to windCeiling +vec3 computeZoneWindDisplacement(vec3 worldPos, int materialData, vec3 normal) { + int windMode = (materialData >> MATERIAL_FLAG_WIND_SWAYING) & 0x7; + if (windMode <= WIND_DISPLACEMENT_DISABLED) + return vec3(0); + + // Approximate model-level values using snapped vertex position + const float ModelSnapping = 512.0; + vec2 snappedPos = snap(worldPos.xz, ModelSnapping); + + // Object-level noise (mirrors comp.glsl lines 67-68) + float modelNoise = noise((snappedPos + vec2(windOffset)) * WIND_DISPLACEMENT_NOISE_RESOLUTION); + float angle = modelNoise * (PI / 2.0); + float c = cos(angle); + float s = sin(angle); + + vec3 windDir = normalize(vec3( + windDirectionX * c + windDirectionZ * s, + 0.0, + -windDirectionX * s + windDirectionZ * c + )); + + // Height-based strength approximation + float heightBasedStrength = saturate(abs(worldPos.y) / windCeiling) * windStrength; + vec3 objectDisplacement = windDir * (heightBasedStrength * modelNoise); + + // Per-vertex strength + float vertexStrength = saturate(abs(worldPos.y) / windCeiling); + if ((materialData >> MATERIAL_FLAG_INVERT_DISPLACEMENT_STRENGTH & 1) == 1) + vertexStrength = 1.0 - vertexStrength; + + float modifierMod = getWindDisplacementMod(materialData); + vec3 displacement = vec3(0); + +#if WIND_DISPLACEMENT + if (windMode >= WIND_DISPLACEMENT_VERTEX) { + const float VertexSnapping = 150.0; + const float VertexDisplacementMod = 0.2; + float windNoise = mix(-0.5, 0.5, noise( + (snap(worldPos, VertexSnapping).xz + vec2(windOffset)) * WIND_DISPLACEMENT_NOISE_RESOLUTION + )); + + if (windMode == WIND_DISPLACEMENT_VERTEX_WITH_HEMISPHERE_BLEND) { + // Hemisphere blend: approximate local-space distances using snapped model center + const float minDist = 50.0; + const float blendDist = 10.0; + vec3 localPos = worldPos - vec3(snappedPos.x, 0, snappedPos.y); + float distBlend = saturate(((abs(localPos.x) + abs(localPos.z)) - minDist) / blendDist); + float heightFade = saturate((vertexStrength - 0.5) / 0.2); + vertexStrength *= mix(0.0, mix(distBlend, 1.0, heightFade), step(0.3, vertexStrength)); + + displacement = windNoise * heightBasedStrength * vertexStrength * VertexDisplacementMod * windDir; + vertexStrength = saturate(vertexStrength - VertexDisplacementMod) * modifierMod; + } else if (windMode == WIND_DISPLACEMENT_VERTEX_JIGGLE) { + vec3 skewA = safe_normalize(cross(normal, vec3(0, 1, 0))); + vec3 skewB = safe_normalize(cross(normal, vec3(1, 0, 0))); + vertexStrength *= modifierMod; + displacement = (windNoise * heightBasedStrength * vertexStrength * 0.5) * skewA; + displacement += ((1.0 - windNoise) * heightBasedStrength * vertexStrength * 0.5) * skewB; + return displacement; + } else { + // VERTEX mode + displacement = windNoise * heightBasedStrength * vertexStrength * VertexDisplacementMod * windDir; + vertexStrength = saturate(vertexStrength - VertexDisplacementMod) * modifierMod; + } + } + + // Object displacement (all modes except JIGGLE) + if (windMode != WIND_DISPLACEMENT_VERTEX_JIGGLE) { + displacement += objectDisplacement * vertexStrength * modifierMod; + } +#endif + + return displacement; +} From 2c96dedb39b18d33367439cf2038cf92e1b154b5 Mon Sep 17 00:00:00 2001 From: John Schultz Date: Tue, 31 Mar 2026 20:09:16 -0400 Subject: [PATCH 9/9] packs the actual model origin (X, Z) into the 16-bit wind data in vNormal.w alongside the existing height ratio and wind reach --- .../rs117/hd/renderer/zone/SceneUploader.java | 1 + src/main/resources/rs117/hd/scene_vert.glsl | 2 +- src/main/resources/rs117/hd/shadow_vert.glsl | 2 +- .../resources/rs117/hd/uniforms/global.glsl | 4 +- src/main/resources/rs117/hd/utils/wind.glsl | 60 ++++++++++++++----- 5 files changed, 50 insertions(+), 19 deletions(-) diff --git a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java index 637b3b5616..dee70f3dd0 100644 --- a/src/main/java/rs117/hd/renderer/zone/SceneUploader.java +++ b/src/main/java/rs117/hd/renderer/zone/SceneUploader.java @@ -140,6 +140,7 @@ public interface OnBeforeProcessTileFunc { private final float[] modelLocal = new float[MAX_VERTEX_COUNT * 3]; private final int[] modelLocalI = new int[MAX_VERTEX_COUNT * 3]; + private final boolean[] visibility = new boolean[MAX_VERTEX_COUNT]; private final ModelOverride[] faceOverrides = new ModelOverride[MAX_FACE_COUNT]; diff --git a/src/main/resources/rs117/hd/scene_vert.glsl b/src/main/resources/rs117/hd/scene_vert.glsl index ea998a6c22..649d66517e 100644 --- a/src/main/resources/rs117/hd/scene_vert.glsl +++ b/src/main/resources/rs117/hd/scene_vert.glsl @@ -111,7 +111,7 @@ layout (location = 0) in vec3 vPosition; } #if WIND_DISPLACEMENT - worldPosition += computeZoneWindDisplacement(worldPosition, materialData, worldNormal); + // TODO: wind data needs a new vertex slot — vNormal.w is now modelIdx #endif OUT.position = worldPosition; diff --git a/src/main/resources/rs117/hd/shadow_vert.glsl b/src/main/resources/rs117/hd/shadow_vert.glsl index 4c43c46501..d07ac60f54 100644 --- a/src/main/resources/rs117/hd/shadow_vert.glsl +++ b/src/main/resources/rs117/hd/shadow_vert.glsl @@ -118,7 +118,7 @@ layout (location = 0) in vec3 vPosition; } #if WIND_DISPLACEMENT - worldPosition += computeZoneWindDisplacement(worldPosition, materialData, vec3(0, 1, 0)); + // TODO: wind data needs a new vertex slot — vNormal.w is now modelIdx #endif #if SHADOW_TRANSPARENCY diff --git a/src/main/resources/rs117/hd/uniforms/global.glsl b/src/main/resources/rs117/hd/uniforms/global.glsl index 893015e0aa..65c6e4800e 100644 --- a/src/main/resources/rs117/hd/uniforms/global.glsl +++ b/src/main/resources/rs117/hd/uniforms/global.glsl @@ -53,10 +53,12 @@ layout(std140) uniform UBOGlobal { float lightningBrightness; float elapsedTime; - // Wind uniforms +#if ZONE_RENDERER + // Wind uniforms (zone renderer only; legacy uses UBOCompute) float windDirectionX; float windDirectionZ; float windStrength; float windCeiling; float windOffset; +#endif }; diff --git a/src/main/resources/rs117/hd/utils/wind.glsl b/src/main/resources/rs117/hd/utils/wind.glsl index d292d12d79..247bd87c55 100644 --- a/src/main/resources/rs117/hd/utils/wind.glsl +++ b/src/main/resources/rs117/hd/utils/wind.glsl @@ -11,27 +11,55 @@ #define WIND_DISPLACEMENT_VERTEX_WITH_HEMISPHERE_BLEND 4 #define WIND_DISPLACEMENT_VERTEX_JIGGLE 5 +// Maximum model wind reach value used for quantization (must match Java packWindData) +#define MAX_WIND_REACH 2560.0 +// Model origin grid spacing in world units (must match Java packWindData) +#define MODEL_ORIGIN_SNAP 256.0 + float getWindDisplacementMod(int materialData) { const float modifiers[7] = float[7](0.25, 0.5, 0.75, 1.0, 1.25, 1.5, 2.0); int modifierIdx = (materialData >> MATERIAL_FLAG_WIND_MODIFIER) & 0x7; return modifiers[modifierIdx]; } +// Unpack wind data from the normal W component (16 bits packed as a short). +// Bits 0-5 (6 bits): vertexHeightRatio (0-63, normalized to 0-1) +// Bits 6-9 (4 bits): modelWindReach (0-15, scaled by MAX_WIND_REACH) +// Bits 10-12 (3 bits): model origin X / MODEL_ORIGIN_SNAP, mod 8 +// Bits 13-15 (3 bits): model origin Z / MODEL_ORIGIN_SNAP, mod 8 +void unpackWindData(float packedShort, out float vertexHeightRatio, out float modelWindReach, out vec2 modelOrigin) { + // vNormal.w is read as GL_SHORT (signed -32768..32767). + // Convert to unsigned 0..65535, then extract fields using arithmetic + // to avoid potential driver issues with bitwise operations on signed ints. + float uval = packedShort < 0.0 ? packedShort + 65536.0 : packedShort; + float originZ = floor(uval / 8192.0); + uval -= originZ * 8192.0; + float originX = floor(uval / 1024.0); + uval -= originX * 1024.0; + float reach = floor(uval / 64.0); + float ratio = uval - reach * 64.0; + vertexHeightRatio = ratio / 63.0; + modelWindReach = reach / 15.0 * MAX_WIND_REACH; + modelOrigin = vec2(originX, originZ) * MODEL_ORIGIN_SNAP; +} + // Compute per-vertex wind displacement for the zone renderer. -// This is a vertex-shader adaptation of the compute-shader wind in priority_render.glsl. -// Since we lack per-model info (model position, model height), we approximate: -// - Model-level noise: snap vertex world XZ to a coarse grid for coherent per-model motion -// - Height-based strength: derived from vertex world Y position relative to windCeiling -vec3 computeZoneWindDisplacement(vec3 worldPos, int materialData, vec3 normal) { +// Closely mirrors the legacy compute-shader wind in comp.glsl + priority_render.glsl. +// packedWindData: raw vNormal.w value containing packed height ratio + model wind reach +vec3 computeZoneWindDisplacement(vec3 worldPos, int materialData, vec3 normal, float packedWindData) { int windMode = (materialData >> MATERIAL_FLAG_WIND_SWAYING) & 0x7; if (windMode <= WIND_DISPLACEMENT_DISABLED) return vec3(0); - // Approximate model-level values using snapped vertex position - const float ModelSnapping = 512.0; - vec2 snappedPos = snap(worldPos.xz, ModelSnapping); + float vertexHeightRatio; + float modelWindReach; + vec2 modelOrigin; + unpackWindData(packedWindData, vertexHeightRatio, modelWindReach, modelOrigin); + + // Use packed model origin for object-level noise (matches legacy's exact model origin) + vec2 snappedPos = modelOrigin; - // Object-level noise (mirrors comp.glsl lines 67-68) + // Object-level noise (mirrors comp.glsl) float modelNoise = noise((snappedPos + vec2(windOffset)) * WIND_DISPLACEMENT_NOISE_RESOLUTION); float angle = modelNoise * (PI / 2.0); float c = cos(angle); @@ -43,12 +71,14 @@ vec3 computeZoneWindDisplacement(vec3 worldPos, int materialData, vec3 normal) { -windDirectionX * s + windDirectionZ * c )); - // Height-based strength approximation - float heightBasedStrength = saturate(abs(worldPos.y) / windCeiling) * windStrength; + // Object-level strength: matches legacy formula exactly + // Legacy: saturate((abs(modelY) + modelHeight) / windCeiling) * windStrength + float heightBasedStrength = saturate(modelWindReach / windCeiling) * windStrength; vec3 objectDisplacement = windDir * (heightBasedStrength * modelNoise); - // Per-vertex strength - float vertexStrength = saturate(abs(worldPos.y) / windCeiling); + // Per-vertex strength: matches legacy saturate(abs(localVertexY) / modelHeight) + float vertexStrength = vertexHeightRatio; + if ((materialData >> MATERIAL_FLAG_INVERT_DISPLACEMENT_STRENGTH & 1) == 1) vertexStrength = 1.0 - vertexStrength; @@ -71,9 +101,7 @@ vec3 computeZoneWindDisplacement(vec3 worldPos, int materialData, vec3 normal) { float distBlend = saturate(((abs(localPos.x) + abs(localPos.z)) - minDist) / blendDist); float heightFade = saturate((vertexStrength - 0.5) / 0.2); vertexStrength *= mix(0.0, mix(distBlend, 1.0, heightFade), step(0.3, vertexStrength)); - - displacement = windNoise * heightBasedStrength * vertexStrength * VertexDisplacementMod * windDir; - vertexStrength = saturate(vertexStrength - VertexDisplacementMod) * modifierMod; + // No vertex displacement — legacy only uses object displacement for this mode } else if (windMode == WIND_DISPLACEMENT_VERTEX_JIGGLE) { vec3 skewA = safe_normalize(cross(normal, vec3(0, 1, 0))); vec3 skewB = safe_normalize(cross(normal, vec3(1, 0, 0)));