Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
cmake_minimum_required(VERSION 2.8.12)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)


cmake_minimum_required(VERSION 3.20)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake")

project(cis565_project5_vulkan_grass_rendering)
Expand Down
59 changes: 51 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,12 +1,55 @@
Vulkan Grass Rendering
==================================
# Vulkan Grass Rendering

**University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 5**
**University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 5**
**Author:** Yi Liu

* (TODO) YOUR NAME HERE
* Tested on: (TODO) Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (Moore 2222 Lab)
## Tested On

- **OS:** Windows 11 Home, Version 24H2 (OS Build 26100.4061)
- **CPU:** Intel(R) Core(TM) i9-14900K @ 3.20GHz, 24 cores / 32 threads
- **RAM:** 64 GB
- **GPU:** NVIDIA GeForce RTX 4090, 24 GB VRAM
- **Environment:** Visual Studio 2022, CUDA 12.6, CMake 3.27

---

This project implements real-time grass simulation and rendering using Vulkan. Grass blades are modeled as dynamic quadratic Bézier curves and animated using GPU-based compute shaders. The pipeline features frustum, distance, and orientation-based culling, as well as tessellation shaders for efficient geometry amplification.

---

## Showcase

The following GIFs demonstrate real-time simulation and rendering of thousands of animated grass blades:

![Responsive Grass](demo/demo.gif)
*Compute-based animation and tessellation-driven rendering*

![Distance Culling](demo/distanceCulling.gif)
*Grass tiles smoothly culled by distance from the camera*

---

## Key Features

- **Physics-Based Animation**
Grass blades bend and sway in response to wind and gravity, simulated entirely on the GPU using compute shaders that update Bézier control points.

- **Bézier Curve Representation**
Each blade is defined as a quadratic Bézier curve with randomized properties (height, width, orientation, stiffness), allowing dynamic, varied blade geometry.

- **Tessellation Pipeline**
Vulkan tessellation control and evaluation shaders convert each animated Bézier curve into screen-space geometry at runtime.

- **Culling Optimizations**
- *View-Frustum Culling*: Discards blades outside the camera’s view.
- *Orientation Culling*: Removes blades facing away from the viewer.
- *Distance Culling*: Uses camera distance to reduce density in the far field.

- **Free-Fly Camera Controls**
Interactively explore the grass field with a full 3D camera system:
- `WASD`: Move horizontally
- `Q` / `E`: Move downward / upward
- Scroll Wheel: Zoom in / out
- Right-click + Drag: Rotate the camera viewpoint

### (TODO: Your README)

*DO NOT* leave the README to the last minute! It is a crucial part of the
project, and we will not be able to grade you without a good README.
Binary file added demo/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo/demo.mp4
Binary file not shown.
Binary file added demo/distanceCulling.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added demo/distanceCulling.mkv
Binary file not shown.
5 changes: 3 additions & 2 deletions external/GLFW/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
cmake_minimum_required(VERSION 2.8.12)
cmake_minimum_required(VERSION 3.10)


project(GLFW C)

Expand All @@ -7,7 +8,7 @@ set(CMAKE_LEGACY_CYGWIN_WIN32 OFF)
if (NOT CMAKE_VERSION VERSION_LESS "3.0")
# Until all major package systems have moved to CMake 3,
# we stick with the older INSTALL_NAME_DIR mechanism
cmake_policy(SET CMP0042 OLD)
cmake_policy(SET CMP0042 NEW)
endif()

if (NOT CMAKE_VERSION VERSION_LESS "3.1")
Expand Down
117 changes: 106 additions & 11 deletions src/Blades.cpp
Original file line number Diff line number Diff line change
@@ -1,38 +1,49 @@
#include <vector>
#include <random>

#include "Blades.h"
#include "BufferUtils.h"

float generateRandomFloat() {
return rand() / (float)RAND_MAX;
}

Blades::Blades(Device* device, VkCommandPool commandPool, float planeDim) : Model(device, commandPool, {}, {}) {

Blades::Blades(Device* device, VkCommandPool commandPool, float tileSize, float tileOffsetX, float tileOffsetZ)
: Model(device, commandPool, {}, {}) {
std::vector<Blade> blades;
blades.reserve(NUM_BLADES);

// Deterministic seed based on tile offset
unsigned int seed = static_cast<unsigned int>(
static_cast<int>(tileOffsetX * 1000.0f) ^ static_cast<int>(tileOffsetZ * 1000.0f)
);
std::mt19937 rng(seed);
std::uniform_real_distribution<float> dist(0.0f, 1.0f);

for (int i = 0; i < NUM_BLADES; i++) {
Blade currentBlade = Blade();

glm::vec3 bladeUp(0.0f, 1.0f, 0.0f);

// Generate positions and direction (v0)
float x = (generateRandomFloat() - 0.5f) * planeDim;
float y = 0.0f;
float z = (generateRandomFloat() - 0.5f) * planeDim;
float direction = generateRandomFloat() * 2.f * 3.14159265f;
float x = (dist(rng) - 0.5f) * tileSize + tileOffsetX;
float z = (dist(rng) - 0.5f) * tileSize + tileOffsetZ;
float y = NoiseUtils::Noise(x * 0.5f, z * 0.5f) * 2.0f;
float direction = dist(rng) * 2.f * 3.14159265f;
glm::vec3 bladePosition(x, y, z);
currentBlade.v0 = glm::vec4(bladePosition, direction);

// Bezier point and height (v1)
float height = MIN_HEIGHT + (generateRandomFloat() * (MAX_HEIGHT - MIN_HEIGHT));
float height = MIN_HEIGHT + dist(rng) * (MAX_HEIGHT - MIN_HEIGHT);
currentBlade.v1 = glm::vec4(bladePosition + bladeUp * height, height);

// Physical model guide and width (v2)
float width = MIN_WIDTH + (generateRandomFloat() * (MAX_WIDTH - MIN_WIDTH));
float width = MIN_WIDTH + dist(rng) * (MAX_WIDTH - MIN_WIDTH);
currentBlade.v2 = glm::vec4(bladePosition + bladeUp * height, width);

// Up vector and stiffness coefficient (up)
float stiffness = MIN_BEND + (generateRandomFloat() * (MAX_BEND - MIN_BEND));
float stiffness = MIN_BEND + dist(rng) * (MAX_BEND - MIN_BEND);
currentBlade.up = glm::vec4(bladeUp, stiffness);

blades.push_back(currentBlade);
Expand All @@ -44,9 +55,82 @@ Blades::Blades(Device* device, VkCommandPool commandPool, float planeDim) : Mode
indirectDraw.firstVertex = 0;
indirectDraw.firstInstance = 0;

BufferUtils::CreateBufferFromData(device, commandPool, blades.data(), NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, bladesBuffer, bladesBufferMemory);
BufferUtils::CreateBuffer(device, NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, culledBladesBuffer, culledBladesBufferMemory);
BufferUtils::CreateBufferFromData(device, commandPool, &indirectDraw, sizeof(BladeDrawIndirect), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, numBladesBuffer, numBladesBufferMemory);
BufferUtils::CreateBufferFromData(device, commandPool, blades.data(), NUM_BLADES * sizeof(Blade),
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, bladesBuffer, bladesBufferMemory);
BufferUtils::CreateBuffer(device, NUM_BLADES * sizeof(Blade),
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, culledBladesBuffer, culledBladesBufferMemory);
BufferUtils::CreateBufferFromData(device, commandPool, &indirectDraw, sizeof(BladeDrawIndirect),
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT,
numBladesBuffer, numBladesBufferMemory);

BufferUtils::CreateBuffer(device, sizeof(TransformationInfo), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
transformBuffer, transBufferMemory);
vkMapMemory(device->GetVkDevice(), transBufferMemory, 0, sizeof(TransformationInfo), 0, &data);
memcpy(data, &transformData, sizeof(TransformationInfo));
vkUnmapMemory(device->GetVkDevice(), transBufferMemory);
}




//Blades::Blades(Device* device, VkCommandPool commandPool, float tileSize, float tileOffsetX, float tileOffsetZ) : Model(device, commandPool, {}, {}) {
// std::vector<Blade> blades;
// blades.reserve(NUM_BLADES);
//
// for (int i = 0; i < NUM_BLADES; i++) {
// Blade currentBlade = Blade();
//
// glm::vec3 bladeUp(0.0f, 1.0f, 0.0f);
//
// // Generate positions and direction (v0)
// float x = (generateRandomFloat() - 0.5f) * tileSize + tileOffsetX;
// float z = (generateRandomFloat() - 0.5f) * tileSize + tileOffsetZ;
// //float y = 0.0f;
// float y = NoiseUtils::Noise(x * 0.5f, z * 0.5f) * 2.0f; // scale coords & height
// float direction = generateRandomFloat() * 2.f * 3.14159265f;
// glm::vec3 bladePosition(x, y, z);
// currentBlade.v0 = glm::vec4(bladePosition, direction);
//
// // Bezier point and height (v1)
// float height = MIN_HEIGHT + (generateRandomFloat() * (MAX_HEIGHT - MIN_HEIGHT));
// currentBlade.v1 = glm::vec4(bladePosition + bladeUp * height, height);
//
// // Physical model guide and width (v2)
// float width = MIN_WIDTH + (generateRandomFloat() * (MAX_WIDTH - MIN_WIDTH));
// currentBlade.v2 = glm::vec4(bladePosition + bladeUp * height, width);
//
// // Up vector and stiffness coefficient (up)
// float stiffness = MIN_BEND + (generateRandomFloat() * (MAX_BEND - MIN_BEND));
// currentBlade.up = glm::vec4(bladeUp, stiffness);
//
// blades.push_back(currentBlade);
// }
//
// BladeDrawIndirect indirectDraw;
// indirectDraw.vertexCount = NUM_BLADES;
// indirectDraw.instanceCount = 1;
// indirectDraw.firstVertex = 0;
// indirectDraw.firstInstance = 0;
//
// BufferUtils::CreateBufferFromData(device, commandPool, blades.data(), NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT, bladesBuffer, bladesBufferMemory);
// BufferUtils::CreateBuffer(device, NUM_BLADES * sizeof(Blade), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT , culledBladesBuffer, culledBladesBufferMemory);
// BufferUtils::CreateBufferFromData(device, commandPool, &indirectDraw, sizeof(BladeDrawIndirect), VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT, numBladesBuffer, numBladesBufferMemory);
//
//
// BufferUtils::CreateBuffer(device, sizeof(TransformationInfo), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, transformBuffer, transBufferMemory);
// vkMapMemory(device->GetVkDevice(), transBufferMemory, 0, sizeof(TransformationInfo), 0, &data);
// memcpy(data, &transformData, sizeof(TransformationInfo));
// vkUnmapMemory(device->GetVkDevice(), transBufferMemory);
//}

void Blades::UpdateTransformation(const glm::vec4 transform) {
transformData.transform = transform;

vkMapMemory(device->GetVkDevice(), transBufferMemory, 0, sizeof(TransformationInfo), 0, &data);
memcpy(data, &transformData, sizeof(TransformationInfo));
vkUnmapMemory(device->GetVkDevice(), transBufferMemory);
}

VkBuffer Blades::GetBladesBuffer() const {
Expand All @@ -61,11 +145,22 @@ VkBuffer Blades::GetNumBladesBuffer() const {
return numBladesBuffer;
}

VkBuffer Blades::GetTransformationBuffer() const {
return transformBuffer;
}

TransformationInfo Blades::GetTransformationData() const
{
return transformData;
}

Blades::~Blades() {
vkDestroyBuffer(device->GetVkDevice(), bladesBuffer, nullptr);
vkFreeMemory(device->GetVkDevice(), bladesBufferMemory, nullptr);
vkDestroyBuffer(device->GetVkDevice(), culledBladesBuffer, nullptr);
vkFreeMemory(device->GetVkDevice(), culledBladesBufferMemory, nullptr);
vkDestroyBuffer(device->GetVkDevice(), numBladesBuffer, nullptr);
vkFreeMemory(device->GetVkDevice(), numBladesBufferMemory, nullptr);
vkDestroyBuffer(device->GetVkDevice(), transformBuffer, nullptr);
vkFreeMemory(device->GetVkDevice(), transBufferMemory, nullptr);
}
45 changes: 36 additions & 9 deletions src/Blades.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
#include <glm/glm.hpp>
#include <array>
#include "Model.h"
#include "NoiseUtils.h"

constexpr static unsigned int NUM_BLADES = 1 << 13;
constexpr static unsigned int NUM_BLADES = 1 << 15;
constexpr static float MIN_HEIGHT = 1.3f;
constexpr static float MAX_HEIGHT = 2.5f;
constexpr static float MIN_WIDTH = 0.1f;
Expand All @@ -22,42 +23,55 @@ struct Blade {
// Up vector and stiffness coefficient
glm::vec4 up;

int bladeType = 2; // blade shape type
int pad0; // Padding to maintain 16-byte alignment
int pad1;
int pad2;

static VkVertexInputBindingDescription getBindingDescription() {
VkVertexInputBindingDescription bindingDescription = {};
bindingDescription.binding = 0;
bindingDescription.binding = 1;
bindingDescription.stride = sizeof(Blade);
bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;

return bindingDescription;
}

static std::array<VkVertexInputAttributeDescription, 4> getAttributeDescriptions() {
std::array<VkVertexInputAttributeDescription, 4> attributeDescriptions = {};
static std::array<VkVertexInputAttributeDescription, 5> getAttributeDescriptions() {
std::array<VkVertexInputAttributeDescription, 5> attributeDescriptions = {};

// v0
attributeDescriptions[0].binding = 0;
attributeDescriptions[0].binding = 1;
attributeDescriptions[0].location = 0;
attributeDescriptions[0].format = VK_FORMAT_R32G32B32A32_SFLOAT;
attributeDescriptions[0].offset = offsetof(Blade, v0);

// v1
attributeDescriptions[1].binding = 0;
attributeDescriptions[1].binding = 1;
attributeDescriptions[1].location = 1;
attributeDescriptions[1].format = VK_FORMAT_R32G32B32A32_SFLOAT;
attributeDescriptions[1].offset = offsetof(Blade, v1);

// v2
attributeDescriptions[2].binding = 0;
attributeDescriptions[2].binding = 1;
attributeDescriptions[2].location = 2;
attributeDescriptions[2].format = VK_FORMAT_R32G32B32A32_SFLOAT;
attributeDescriptions[2].offset = offsetof(Blade, v2);

// up
attributeDescriptions[3].binding = 0;
attributeDescriptions[3].binding = 1;
attributeDescriptions[3].location = 3;
attributeDescriptions[3].format = VK_FORMAT_R32G32B32A32_SFLOAT;
attributeDescriptions[3].offset = offsetof(Blade, up);


// bladeType
attributeDescriptions[4].binding = 1;
attributeDescriptions[4].location = 4;
attributeDescriptions[4].format = VK_FORMAT_R32_SINT; // 1 int
attributeDescriptions[4].offset = offsetof(Blade, bladeType);


return attributeDescriptions;
}
};
Expand All @@ -69,20 +83,33 @@ struct BladeDrawIndirect {
uint32_t firstInstance;
};

struct TransformationInfo {
glm::vec4 transform;
};

class Blades : public Model {
private:
VkBuffer bladesBuffer;
VkBuffer culledBladesBuffer;
VkBuffer numBladesBuffer;
VkBuffer transformBuffer;

VkDeviceMemory bladesBufferMemory;
VkDeviceMemory culledBladesBufferMemory;
VkDeviceMemory numBladesBufferMemory;
VkDeviceMemory transBufferMemory;

void* data;
TransformationInfo transformData;

public:
Blades(Device* device, VkCommandPool commandPool, float planeDim);
Blades(Device* device, VkCommandPool commandPool, float tileSize, float tileOffsetX, float tileOffsetZ);
VkBuffer GetBladesBuffer() const;
VkBuffer GetCulledBladesBuffer() const;
VkBuffer GetNumBladesBuffer() const;

VkBuffer GetTransformationBuffer() const;
TransformationInfo GetTransformationData() const;
void UpdateTransformation(const glm::vec4 transformation);
~Blades();
};
Loading