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
272 changes: 62 additions & 210 deletions README.md

Large diffs are not rendered by default.

Binary file added img/Performance.png
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 img/Workgroup.png
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 img/buckets.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 removed img/cube_demo.png
Binary file not shown.
Binary file added img/final.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 img/firstOutput.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file renamed img/grass.gif → img/frustumculling.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 removed img/grass2.gif
Binary file not shown.
Binary file removed img/grass_basic.gif
Binary file not shown.
Binary file added img/initial.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 img/manyBlades.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 img/progress.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 img/start.png
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 img/viewFrustumCulling.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 img/windy.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/Blades.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#include <array>
#include "Model.h"

constexpr static unsigned int NUM_BLADES = 1 << 13;
constexpr static unsigned int NUM_BLADES = 1 << 14;
constexpr static float MIN_HEIGHT = 1.3f;
constexpr static float MAX_HEIGHT = 2.5f;
constexpr static float MIN_WIDTH = 0.1f;
Expand Down
311 changes: 287 additions & 24 deletions src/Renderer.cpp

Large diffs are not rendered by default.

15 changes: 13 additions & 2 deletions src/Renderer.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,19 @@ class Renderer {
void CreateCameraDescriptorSetLayout();
void CreateModelDescriptorSetLayout();
void CreateTimeDescriptorSetLayout();
void CreateComputeDescriptorSetLayout();
void CreateComputeBladesDescriptorSetLayout();
void CreateComputeCulledBladesDescriptorSetLayout();
void CreateComputeNumBladesDescriptorSetLayout();

void CreateDescriptorPool();

void CreateCameraDescriptorSet();
void CreateModelDescriptorSets();
void CreateGrassDescriptorSets();
void CreateTimeDescriptorSet();
void CreateComputeDescriptorSets();
void CreateComputeBladesDescriptorSets();
void CreateComputeCulledBladesDescriptorSets();
void CreateComputeNumBladesDescriptorSets();

void CreateGraphicsPipeline();
void CreateGrassPipeline();
Expand Down Expand Up @@ -56,11 +60,18 @@ class Renderer {
VkDescriptorSetLayout cameraDescriptorSetLayout;
VkDescriptorSetLayout modelDescriptorSetLayout;
VkDescriptorSetLayout timeDescriptorSetLayout;
VkDescriptorSetLayout computeBladesDescriptorSetLayout;
VkDescriptorSetLayout computeCulledBladesDescriptorSetLayout;
VkDescriptorSetLayout computeNumBladesDescriptorSetLayout;

VkDescriptorPool descriptorPool;

VkDescriptorSet cameraDescriptorSet;
std::vector<VkDescriptorSet> modelDescriptorSets;
std::vector<VkDescriptorSet> computeBladesDescriptorSets;
std::vector<VkDescriptorSet> computeCulledBladesDescriptorSets;
std::vector<VkDescriptorSet> computeNumBladesDescriptorSets;
std::vector<VkDescriptorSet> grassDescriptorSets;
VkDescriptorSet timeDescriptorSet;

VkPipelineLayout graphicsPipelineLayout;
Expand Down
2 changes: 2 additions & 0 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ namespace {
}

int main() {
//while (!GetAsyncKeyState(VK_F12));

static constexpr char* applicationName = "Vulkan Grass Rendering";
InitializeWindow(640, 480, applicationName);

Expand Down
150 changes: 150 additions & 0 deletions src/notes.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
Notes on the writeup (and I guess also the paper):

So basically we're doing two things. We render the grass and we also build a simulator for having wind forces on the grass.

The rendering is made in tesselation shaders (etc.) and the physics simulation happens in the compute shaders

The files are like:
shaders/compute.comp -- computes the physics calculations of wind on the bezier curve
shaders/grass.vert -- computes the transform (?)
shaders/grass.tesc -- controls the tessellation of the grass
shaders/grass.tese -- evaluates the tessellation of the grass (i.e going from a single bezier curve to an actual set of vertices)

Bezier Curve Representation:

We will basically be storing three vertices (vec3) one up vector (vec3) then 4 parameters (orientation, height, width and stiffness).
We can pack them together so that's only 4 vec4s when passing through vertex shader
v0.w = orientation,
v1.w = height,
v2.w = width,
up.w = stiffness

We simulate the forces given the bezier curve input -- we really only apply forces to v2 and then use v1 to maintain the appropriate height/length of blade
v0 is our base ground position so that's not going to change with something like wind.

We are in charge of maintaining the storage and uploading of all the grass data :( so we gotta make a buffer.
The buffer should include: -- amount of time passed in simulation
-- amount of time since last frame (in order to do both they say to extend/create descriptor sets
that will be bound to the compute pipeline, but what the hell does that mean)

Okay so the forces that we have to implement in the compute shader are threefold:

Gravity, Recovery, and WIND

Gravity -- Given a gravity vector D (direction xyz magnitude w): gE = normalize(D.xyz) * D.w
- then we compute the "Front gravity" (f=front facing direction of the blade): gF = (1/4) * ||gE|| * f
- g = gE + gF

Recovery -- Basically we gotta find the initial value of v1/v2 (let that be iv2). (probably just going to walk along the up vector by height of blade) from v0.
- Once we have iv2, then we're golden: r = (iv2 - v2) * stiffness

Wind -- Try to do some sine or cosine function (maybe one that depends on the position v0 & changes with time)

---------------------------
Total TODOs in the codebase:
Renderer.cpp -
198 TODO: Create the descriptor set layout for the compute pipeline
// Remember this is like a class definition stating why types of information
// will be stored at each binding
218 // TODO: Add any additional types and counts of descriptors you will need to allocate
320 // TODO: Create Descriptor sets for the grass.
// This should involve creating descriptor sets which point to the model matrix of each group of grass blades
360 // TODO: Create Descriptor sets for the compute pipeline
// The descriptors should point to Storage buffers which will hold the grass blades, the culled grass blades, and the output number of grass blades
719 // TODO: Add the compute dsecriptor set layout you create to this list
886 // TODO: For each group of blades bind its descriptor set and dispatch
978 // TODO: Uncomment this when the buffers are populated
// vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets);

// TODO: Bind the descriptor set for each grass blades model

// Draw
// TODO: Uncomment this when the buffers are populated
// vkCmdDrawIndirect(commandBuffers[i], scene->GetBlades()[j]->GetNumBladesBuffer(), 0, 1, sizeof(BladeDrawIndirect));
1044 // TODO: destroy any resources you created

compute.comp -
24 // TODO: Add bindings to:
// 1. Store the input blades
// 2. Write out the culled blades
// 3. Write the total number of blades remaining

// The project is using vkCmdDrawIndirect to use a buffer as the arguments for a draw call
// This is sort of an advanced feature so we've showed you what this buffer should look like
//
// layout(set = ???, binding = ???) buffer NumBlades {
// uint vertexCount; // Write the number of blades remaining here
// uint instanceCount; // = 1
// uint firstVertex; // = 0
// uint firstInstance; // = 0
// } numBlades;

50 // TODO: Apply forces on every blade and update the vertices in the buffer

// TODO: Cull blades that are too far away or not in the camera frustum and write them
// to the culled blades buffer
// Note: to do this, you will need to use an atomic operation to read and update numBlades.vertexCount
// You want to write the visible blades to the buffer without write conflicts between threads

grass.frag -
9 // TODO: Declare fragment shader inputs
14 // TODO: Compute fragment color

grass.tesc -
11 // TODO: Declare tessellation control shader inputs and outputs
17 // TODO: Write any shader outputs

// TODO: Set level of tesselation
// gl_TessLevelInner[0] = ???
// gl_TessLevelInner[1] = ???
// gl_TessLevelOuter[0] = ???
// gl_TessLevelOuter[1] = ???
// gl_TessLevelOuter[2] = ???
// gl_TessLevelOuter[3] = ???

grass.tese -
11 // TODO: Declare tessellation evaluation shader inputs and outputs
17 // TODO: Use u and v to parameterize along the grass blade and output positions for each vertex of the grass blade

grass.vert -
9 // TODO: Declare vertex shader inputs and outputs
16 // TODO: Write gl_Position and any other shader outputs

So in order to allocate the grass descriptor set we need::
vkDescriptorSetLayout
vkDescriptorSetAllovateInfo

then call vkAllocateDescriptorSets

VkDescriptorBufferInfo

// Structure specifying descriptor buffer info
typedef struct VkDescriptorBufferInfo {
VkBuffer buffer;
VkDeviceSize offset;
VkDeviceSize range;
} VkDescriptorBufferInfo;

std::array<VkWriteDescriptorSet, 1>

//Structure specifying the parameters of a descriptor set write operation
typedef struct VkWriteDescriptorSet {
VkStructureType sType;
const void* pNext;
VkDescriptorSet dstSet;
uint32_t dstBinding;
uint32_t dstArrayElement;
uint32_t descriptorCount;
VkDescriptorType descriptorType;
const VkDescriptorImageInfo* pImageInfo;
const VkDescriptorBufferInfo* pBufferInfo;
const VkBufferView* pTexelBufferView;
} VkWriteDescriptorSet;

then call vkUpdateDescriptorSets
Update the contents of a descriptor set object
VkDevice device,
uint32_t descriptorWriteCount,
const VkWriteDescriptorSet* pDescriptorWrites,
uint32_t descriptorCopyCount,
const VkCopyDescriptorSet* pDescriptorCopies); ~ null
145 changes: 125 additions & 20 deletions src/shaders/compute.comp
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@
#extension GL_ARB_separate_shader_objects : enable

#define WORKGROUP_SIZE 32
#define GRAVITY_ACCEL 5
#define WIND_AMPLITUDE 10

#define ORIENTATION_TOLERANCE 0.8
#define VIEW_FRUSTUM_TOLERANCE 2.0
#define MAX_DISTANCE 40.0
#define NUM_BUCKETS 30

layout(local_size_x = WORKGROUP_SIZE, local_size_y = 1, local_size_z = 1) in;

layout(set = 0, binding = 0) uniform CameraBufferObject {
Expand All @@ -21,20 +29,20 @@ struct Blade {
vec4 up;
};

// TODO: Add bindings to:
// 1. Store the input blades
// 2. Write out the culled blades
// 3. Write the total number of blades remaining

// The project is using vkCmdDrawIndirect to use a buffer as the arguments for a draw call
// This is sort of an advanced feature so we've showed you what this buffer should look like
//
// layout(set = ???, binding = ???) buffer NumBlades {
// uint vertexCount; // Write the number of blades remaining here
// uint instanceCount; // = 1
// uint firstVertex; // = 0
// uint firstInstance; // = 0
// } numBlades;
layout(set = 2, binding = 0) buffer Blades {
Blade data[];
} bladeData;

layout(set = 3, binding = 0) buffer CulledBlades {
Blade data[];
} culledBladeData;

layout(set = 4, binding = 0) buffer NumBlades {
uint vertexCount; // Write the number of blades remaining here
uint instanceCount; // = 1
uint firstVertex; // = 0
uint firstInstance; // = 0
} numBlades;

bool inBounds(float value, float bounds) {
return (value >= -bounds) && (value <= bounds);
Expand All @@ -43,14 +51,111 @@ bool inBounds(float value, float bounds) {
void main() {
// Reset the number of blades to 0
if (gl_GlobalInvocationID.x == 0) {
// numBlades.vertexCount = 0;
numBlades.vertexCount = 0;
}
barrier(); // Wait till all threads reach this point

// TODO: Apply forces on every blade and update the vertices in the buffer
// Apply forces on every blade and update the vertices in the buffer
Blade b = bladeData.data[gl_GlobalInvocationID.x];

// Extract the blade constants
float orientation = b.v0.w;
float height = b.v1.w;
float width = b.v2.w;
float stiffness = b.up.w;

// Extract the blade vertices
vec3 v0 = b.v0.xyz;
vec3 v1 = b.v1.xyz;
vec3 v2 = b.v2.xyz;
vec3 up = b.up.xyz;

// calculate the front facing direction of the blade
// NOTE: we're assuming here that "orientation" is an angle in radians
// relative to the vector vec3(1.0, 0.0, 0.0);
vec3 front = vec3(cos(orientation), 0.0, sin(orientation));

// calculate gravity
vec3 gravityDir = vec3(0.0, -1.0, 0.0);
vec3 gravity = normalize(gravityDir) * GRAVITY_ACCEL;
vec3 frontGravity = 0.25 * length(gravity) * front;
vec3 g = gravity + frontGravity;

// Calculate Recovery
vec3 iv2 = v0 + up * height;
vec3 r = (iv2 - v2) * stiffness;

// Calculate Wind Influence (we're doing a simple ripple here)
float windStrength = WIND_AMPLITUDE * sin(v0.x + v0.z + (totalTime / 2.5));
vec3 windVector = abs(windStrength) * vec3(-1.0, 0.0, 0.0);

// now get the directional alignment and the height ratio
float directionalAlignment = 1 - abs(dot((windVector) / length(windVector), (v2 - v0) / length(v2 - v0)));
float heightRatio = (dot((v2 - v0), up) / height);
float alignmentValue = directionalAlignment * heightRatio;
vec3 w = alignmentValue * windVector;

// TODO: Cull blades that are too far away or not in the camera frustum and write them
// to the culled blades buffer
// Note: to do this, you will need to use an atomic operation to read and update numBlades.vertexCount
// You want to write the visible blades to the buffer without write conflicts between threads
// now calculate how much v2 moves based on the forces and the delta time
vec3 tv2 = (g + r + w) * deltaTime;
vec3 newv2 = v2 + tv2;

// validate that v2 hasn't sunk beneath the 3d model
newv2 = newv2 - (up * min(dot(up, (newv2 - v0)), 0.0));

// calculate the new value for v1
float proj = length(newv2 - v0 - (dot(up, (newv2 - v0)) * up));
vec3 newv1 = v0 + height * up * max(1 - (proj/height), 0.05 * max(proj/height, 1.0));

// approximate the length of our bezier curve
float n = 1.0; // the degree of our bezier curve
float L0 = length(newv2 - v0);
float L1 = length(newv2 - newv1) + length(newv1 - v0);
float L = (2 * L0 + (n-1) * L1) / (n+1);

// correct the points to prevent that length from changing
float ratio = height / L;
vec3 v1corr = v0 + ratio * (newv1 - v0);
vec3 v2corr = v1corr + ratio * (newv2 - newv1);

// Update the blades with their new v1 and v2 position
bladeData.data[gl_GlobalInvocationID.x].v1 = vec4(v1corr, height);
bladeData.data[gl_GlobalInvocationID.x].v2 = vec4(v2corr, width);

//// Perform orientation cull test
bool isFacingCamera = abs(dot(camera.view * vec4(cross(up, front), 0.0), vec4(0.0, 0.0, -1.0, 0.0))) > ORIENTATION_TOLERANCE;

//// Perform view frustum cull test
// get midpoint of the blade
vec3 midpoint = (1.0/4.0) * v0 * (1.0/2.0) * v1corr * (1.0/4.0) * v2corr;

// get the vectors in clip space
vec4 v0Clip = camera.proj * camera.view * vec4(v0, 1.0);
vec4 v2Clip = camera.proj * camera.view * vec4(v2corr, 1.0);
vec4 mClip = camera.proj * camera.view * vec4(midpoint, 1.0);

// determine if they are in view
bool isv0InView = inBounds(v0Clip.x, v0Clip.w + VIEW_FRUSTUM_TOLERANCE) && inBounds(v0Clip.y, v0Clip.w + VIEW_FRUSTUM_TOLERANCE) && inBounds(v0Clip.z, v0Clip.w + VIEW_FRUSTUM_TOLERANCE);
bool isv2InView = inBounds(v2Clip.x, v2Clip.w + VIEW_FRUSTUM_TOLERANCE) && inBounds(v2Clip.y, v2Clip.w + VIEW_FRUSTUM_TOLERANCE) && inBounds(v2Clip.z, v2Clip.w + VIEW_FRUSTUM_TOLERANCE);
bool ismInView = inBounds(mClip.x, mClip.w + VIEW_FRUSTUM_TOLERANCE) && inBounds(mClip.y, mClip.w + VIEW_FRUSTUM_TOLERANCE) && inBounds(mClip.z, mClip.w + VIEW_FRUSTUM_TOLERANCE);

// the blade is in view only if all of them are
bool isInView = isv0InView && isv2InView && ismInView;

//// Perform Distance cull test
vec4 v0View = camera.view * vec4(v0, 1.0);
vec4 upView = normalize(camera.view * vec4(up, 0.0));
float distanceFromCam = length(v0View); //- (dot(v0View, vec4(up, 0.0)) * vec4(up, 0.0)));

// sample based on bucket if blade is culled or not
bool isCloseEnough = (gl_GlobalInvocationID.x % NUM_BUCKETS) < floor(NUM_BUCKETS * (1.0 - (distanceFromCam / MAX_DISTANCE)));

// If the blade passes all of these tests add it to the culledBladeData
if (isFacingCamera && isInView && isCloseEnough) {
uint count = atomicAdd(numBlades.vertexCount, 1);
barrier();
culledBladeData.data[count].v0 = vec4(v0, orientation);
culledBladeData.data[count].v1 = vec4(v1corr, height);
culledBladeData.data[count].v2 = vec4(v2corr, width);
culledBladeData.data[count].up = vec4(up, stiffness);
}
}
6 changes: 2 additions & 4 deletions src/shaders/grass.frag
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ layout(set = 0, binding = 0) uniform CameraBufferObject {
mat4 proj;
} camera;

// TODO: Declare fragment shader inputs
layout(location = 0) in float lightIntensity;

layout(location = 0) out vec4 outColor;

void main() {
// TODO: Compute fragment color

outColor = vec4(1.0);
outColor = lightIntensity * vec4(34.0 / 255.0, 139.0 / 255.0, 34.0 / 255.0, 0.0);
}
Loading