diff --git a/data/shaders/terraintessellation/terrain.frag b/data/shaders/terraintessellation/terrain.frag index c5634c9d..1cde9396 100644 --- a/data/shaders/terraintessellation/terrain.frag +++ b/data/shaders/terraintessellation/terrain.frag @@ -3,17 +3,17 @@ #extension GL_ARB_separate_shader_objects : enable #extension GL_ARB_shading_language_420pack : enable -layout (set = 0, binding = 1) uniform sampler2D displacementMap; -layout (set = 0, binding = 2) uniform sampler2DArray terrainLayers; +layout (set = 0, binding = 1) uniform sampler2D samplerHeight; +layout (set = 0, binding = 2) uniform sampler2DArray samplerLayers; layout (location = 0) in vec3 inNormal; layout (location = 1) in vec2 inUV; -layout (location = 2) in vec3 inEyePos; +layout (location = 2) in vec3 inViewVec; layout (location = 3) in vec3 inLightVec; layout (location = 0) out vec4 outFragColor; -vec4 sampleTerrainLayer() +vec3 sampleTerrainLayer() { // Define some layer ranges for sampling depending on terrain height vec2 layers[6]; @@ -27,33 +27,24 @@ vec4 sampleTerrainLayer() vec3 color = vec3(0.0); // Get height from displacement map - float height = textureLod(displacementMap, inUV, 0.0).r * 255.0; + float height = textureLod(samplerHeight, inUV, 0.0).r * 255.0; for (int i = 0; i < 6; i++) { float range = layers[i].y - layers[i].x; float weight = (range - abs(height - layers[i].y)) / range; weight = max(0.0, weight); - color += weight * texture(terrainLayers, vec3(inUV * 16.0, i)).rgb; + color += weight * texture(samplerLayers, vec3(inUV * 16.0, i)).rgb; } - return vec4(color, 1.0); + return color; } void main() { -/* todo: no lighting yet vec3 N = normalize(inNormal); - vec3 L = normalize(vec3(1.0)); - vec3 Eye = normalize(inEyePos); - vec3 Reflected = normalize(reflect(-inLightVec, inNormal)); - - vec4 IAmbient = vec4(vec3(0.15), 1.0); - vec4 IDiffuse = vec4(1.0) * max(dot(inNormal, inLightVec), 0.0); - - outFragColor = vec4((IAmbient + IDiffuse) * vec4(texture(terrainLayers, vec3(inUV, 0.0)).rgb, 1.0)); -*/ - outFragColor = sampleTerrainLayer(); - - //outFragColor.rgb = normalize(inNormal); + vec3 L = normalize(inLightVec); + vec3 ambient = vec3(0.5); + vec3 diffuse = max(dot(N, L), 0.0) * vec3(1.0); + outFragColor = vec4((ambient + diffuse) * sampleTerrainLayer(), 1.0); } diff --git a/data/shaders/terraintessellation/terrain.frag.spv b/data/shaders/terraintessellation/terrain.frag.spv index 04b71e3e..86bc3465 100644 Binary files a/data/shaders/terraintessellation/terrain.frag.spv and b/data/shaders/terraintessellation/terrain.frag.spv differ diff --git a/data/shaders/terraintessellation/terrain.tese b/data/shaders/terraintessellation/terrain.tese index 1f90b024..64b0e0c9 100644 --- a/data/shaders/terraintessellation/terrain.tese +++ b/data/shaders/terraintessellation/terrain.tese @@ -24,7 +24,7 @@ layout (location = 1) in vec2 inUV[]; layout (location = 0) out vec3 outNormal; layout (location = 1) out vec2 outUV; -layout (location = 2) out vec3 outEyePos; +layout (location = 2) out vec3 outViewVec; layout (location = 3) out vec3 outLightVec; void main() @@ -34,10 +34,9 @@ void main() vec2 uv2 = mix(inUV[3], inUV[2], gl_TessCoord.x); outUV = mix(uv1, uv2, gl_TessCoord.y); - // Interpolate normals - vec3 nm1 = mix(inNormal[0], inNormal[1], gl_TessCoord.x); - vec3 nm2 = mix(inNormal[3], inNormal[2], gl_TessCoord.x); - outNormal = mix(nm1, nm2, gl_TessCoord.y); + vec3 n1 = mix(inNormal[0], inNormal[1], gl_TessCoord.x); + vec3 n2 = mix(inNormal[3], inNormal[2], gl_TessCoord.x); + outNormal = mix(n1, n2, gl_TessCoord.y); // Interpolate positions vec4 pos1 = mix(gl_in[0].gl_Position, gl_in[1].gl_Position, gl_TessCoord.x); @@ -49,6 +48,6 @@ void main() gl_Position = ubo.projection * ubo.modelview * pos; // Calculate vectors for lighting based on tessellated position - outEyePos = -pos.xyz; - outLightVec = normalize(ubo.lightPos.xyz + outEyePos); + outViewVec = -pos.xyz; + outLightVec = normalize(ubo.lightPos.xyz + outViewVec); } \ No newline at end of file diff --git a/data/shaders/terraintessellation/terrain.tese.spv b/data/shaders/terraintessellation/terrain.tese.spv index a3976020..a49209b0 100644 Binary files a/data/shaders/terraintessellation/terrain.tese.spv and b/data/shaders/terraintessellation/terrain.tese.spv differ diff --git a/terraintessellation/terraintessellation.cpp b/terraintessellation/terraintessellation.cpp index 03f9fd0c..c0899168 100644 --- a/terraintessellation/terraintessellation.cpp +++ b/terraintessellation/terraintessellation.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #define GLM_FORCE_RADIANS #define GLM_FORCE_DEPTH_ZERO_TO_ONE @@ -65,7 +66,7 @@ public: struct { glm::mat4 projection; glm::mat4 modelview; - glm::vec4 lightPos = glm::vec4(0.0f, -2.0f, 0.0f, 0.0f); + glm::vec4 lightPos = glm::vec4(-48.0f, -40.0f, 46.0f, 0.0f); glm::vec4 frustumPlanes[6]; float displacementFactor = 32.0f; float tessellationFactor = 0.75f; @@ -117,10 +118,9 @@ public: title = "Vulkan Example - Dynamic terrain tessellation"; camera.type = Camera::CameraType::firstperson; camera.setPerspective(60.0f, (float)width / (float)height, 0.1f, 512.0f); - camera.setRotation(glm::vec3(-6.0f, -56.0f, 0.0f)); - camera.setTranslation(glm::vec3(-45.0f, 14.0f, -28.5f)); + camera.setRotation(glm::vec3(-11.0f, 56.0f, 0.0f)); + camera.setTranslation(glm::vec3(60.0f, 20.5f, -44.0f)); camera.movementSpeed = 7.5f; - timerSpeed *= 15.0f; // Support for tessellation shaders is optional, so check first //if (!deviceFeatures.tessellationShader) //{ @@ -331,6 +331,38 @@ public: loadMesh(getAssetPath() + "models/geosphere.obj", &meshes.skysphere, vertexLayout, 1.0f); } + // Encapsulate height map data for easy sampling + struct HeightMap + { + private: + uint16_t *heightdata; + uint32_t dim; + uint32_t scale; + public: + HeightMap(std::string filename, uint32_t patchsize) + { + gli::texture2D heightTex(gli::load(filename)); + dim = heightTex.dimensions().x; + heightdata = new uint16_t[dim * dim]; + memcpy(heightdata, heightTex.data(), heightTex.size()); + this->scale = dim / patchsize; + }; + + ~HeightMap() + { + delete[] heightdata; + } + + float getHeight(uint32_t x, uint32_t y) + { + glm::ivec2 rpos = glm::ivec2(x, y) * glm::ivec2(scale); + rpos.x = std::max(0, std::min(rpos.x, (int)dim-1)); + rpos.y = std::max(0, std::min(rpos.y, (int)dim-1)); + rpos /= glm::ivec2(scale); + return *(heightdata + (rpos.x + rpos.y * dim) * scale) / 65535.0f; + } + }; + // Generate a terrain quad patch for feeding to the tessellation control shader void generateTerrain() { @@ -356,11 +388,41 @@ public: vertices[index].pos[0] = x * wx + wx / 2.0f - (float)PATCH_SIZE * wx / 2.0f; vertices[index].pos[1] = 0.0f; vertices[index].pos[2] = y * wy + wy / 2.0f - (float)PATCH_SIZE * wy / 2.0f; - vertices[index].normal = glm::vec3(0.0f, 1.0f, 0.0f); vertices[index].uv = glm::vec2((float)x / PATCH_SIZE, (float)y / PATCH_SIZE) * UV_SCALE; } } + // Calculate normals from height map using a sobel filter + HeightMap heightMap(getAssetPath() + "textures/terrain_heightmap_r16.ktx", PATCH_SIZE); + for (auto x = 0; x < PATCH_SIZE; x++) + { + for (auto y = 0; y < PATCH_SIZE; y++) + { + // Get height samples centered around current position + float heights[3][3]; + for (auto hx = -1; hx <= 1; hx++) + { + for (auto hy = -1; hy <= 1; hy++) + { + heights[hx+1][hy+1] = heightMap.getHeight(x + hx, y + hy); + } + } + + // Calcualte the normal + glm::vec3 normal; + // Gx sobel filter + normal.x = heights[0][0] - heights[2][0] + 2.0f * heights[0][1] - 2.0f * heights[2][1] + heights[0][2] - heights[2][2]; + // Gy sobel filter + normal.z = heights[0][0] + 2.0f * heights[1][0] + heights[2][0] - heights[0][2] - 2.0f * heights[1][2] - heights[2][2]; + // Calculate missing up component of the normal using the filtered x and y axis + // The first value controls the bump strength + normal.y = 0.25f * sqrt( 1.0f - normal.x * normal.x - normal.z * normal.z); + + vertices[x + y * PATCH_SIZE].normal = glm::normalize(normal * glm::vec3(2.0f, 1.0f, 2.0f)); + } + } + + // Indices const uint32_t w = (PATCH_SIZE - 1); uint32_t *indices = new uint32_t[w * w * 4]; for (auto x = 0; x < w; x++) @@ -374,7 +436,6 @@ public: indices[index + 3] = indices[index] + 1; } } - meshes.object.indexCount = (PATCH_SIZE - 1) * (PATCH_SIZE - 1) * 4; uint32_t vertexBufferSize = (PATCH_SIZE * PATCH_SIZE * 4) * sizeof(Vertex);