├── .gitignore
├── LICENSE.md
├── README.md
├── gl4.cpp
├── gl4.h
├── proj1_life3d
├── Makefile
├── README.md
└── main.cpp
├── proj2_tess
├── Makefile
├── README.md
└── main.cpp
├── proj3_nbody
├── Makefile
├── README.md
└── main.cpp
└── proj4_fluid
├── Makefile
├── README.md
└── main.cpp
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | a.out
3 | *.o
4 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | CC0 1.0 Universal
2 |
3 | Statement of Purpose
4 |
5 | The laws of most jurisdictions throughout the world automatically confer
6 | exclusive Copyright and Related Rights (defined below) upon the creator and
7 | subsequent owner(s) (each and all, an "owner") of an original work of
8 | authorship and/or a database (each, a "Work").
9 |
10 | Certain owners wish to permanently relinquish those rights to a Work for the
11 | purpose of contributing to a commons of creative, cultural and scientific
12 | works ("Commons") that the public can reliably and without fear of later
13 | claims of infringement build upon, modify, incorporate in other works, reuse
14 | and redistribute as freely as possible in any form whatsoever and for any
15 | purposes, including without limitation commercial purposes. These owners may
16 | contribute to the Commons to promote the ideal of a free culture and the
17 | further production of creative, cultural and scientific works, or to gain
18 | reputation or greater distribution for their Work in part through the use and
19 | efforts of others.
20 |
21 | For these and/or other purposes and motivations, and without any expectation
22 | of additional consideration or compensation, the person associating CC0 with a
23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright
24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work
25 | and publicly distribute the Work under its terms, with knowledge of his or her
26 | Copyright and Related Rights in the Work and the meaning and intended legal
27 | effect of CC0 on those rights.
28 |
29 | 1. Copyright and Related Rights. A Work made available under CC0 may be
30 | protected by copyright and related or neighboring rights ("Copyright and
31 | Related Rights"). Copyright and Related Rights include, but are not limited
32 | to, the following:
33 |
34 | i. the right to reproduce, adapt, distribute, perform, display, communicate,
35 | and translate a Work;
36 |
37 | ii. moral rights retained by the original author(s) and/or performer(s);
38 |
39 | iii. publicity and privacy rights pertaining to a person's image or likeness
40 | depicted in a Work;
41 |
42 | iv. rights protecting against unfair competition in regards to a Work,
43 | subject to the limitations in paragraph 4(a), below;
44 |
45 | v. rights protecting the extraction, dissemination, use and reuse of data in
46 | a Work;
47 |
48 | vi. database rights (such as those arising under Directive 96/9/EC of the
49 | European Parliament and of the Council of 11 March 1996 on the legal
50 | protection of databases, and under any national implementation thereof,
51 | including any amended or successor version of such directive); and
52 |
53 | vii. other similar, equivalent or corresponding rights throughout the world
54 | based on applicable law or treaty, and any national implementations thereof.
55 |
56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of,
57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and
58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright
59 | and Related Rights and associated claims and causes of action, whether now
60 | known or unknown (including existing as well as future claims and causes of
61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum
62 | duration provided by applicable law or treaty (including future time
63 | extensions), (iii) in any current or future medium and for any number of
64 | copies, and (iv) for any purpose whatsoever, including without limitation
65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes
66 | the Waiver for the benefit of each member of the public at large and to the
67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver
68 | shall not be subject to revocation, rescission, cancellation, termination, or
69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work
70 | by the public as contemplated by Affirmer's express Statement of Purpose.
71 |
72 | 3. Public License Fallback. Should any part of the Waiver for any reason be
73 | judged legally invalid or ineffective under applicable law, then the Waiver
74 | shall be preserved to the maximum extent permitted taking into account
75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver
76 | is so judged Affirmer hereby grants to each affected person a royalty-free,
77 | non transferable, non sublicensable, non exclusive, irrevocable and
78 | unconditional license to exercise Affirmer's Copyright and Related Rights in
79 | the Work (i) in all territories worldwide, (ii) for the maximum duration
80 | provided by applicable law or treaty (including future time extensions), (iii)
81 | in any current or future medium and for any number of copies, and (iv) for any
82 | purpose whatsoever, including without limitation commercial, advertising or
83 | promotional purposes (the "License"). The License shall be deemed effective as
84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the
85 | License for any reason be judged legally invalid or ineffective under
86 | applicable law, such partial invalidity or ineffectiveness shall not
87 | invalidate the remainder of the License, and in such case Affirmer hereby
88 | affirms that he or she will not (i) exercise any of his or her remaining
89 | Copyright and Related Rights in the Work or (ii) assert any associated claims
90 | and causes of action with respect to the Work, in either case contrary to
91 | Affirmer's express Statement of Purpose.
92 |
93 | 4. Limitations and Disclaimers.
94 |
95 | a. No trademark or patent rights held by Affirmer are waived, abandoned,
96 | surrendered, licensed or otherwise affected by this document.
97 |
98 | b. Affirmer offers the Work as-is and makes no representations or warranties
99 | of any kind concerning the Work, express, implied, statutory or otherwise,
100 | including without limitation warranties of title, merchantability, fitness
101 | for a particular purpose, non infringement, or the absence of latent or
102 | other defects, accuracy, or the present or absence of errors, whether or not
103 | discoverable, all to the greatest extent permissible under applicable law.
104 |
105 | c. Affirmer disclaims responsibility for clearing rights of other persons
106 | that may apply to the Work or any use thereof, including without limitation
107 | any person's Copyright and Related Rights in the Work. Further, Affirmer
108 | disclaims responsibility for obtaining any necessary consents, permissions
109 | or other rights required for any use of the Work.
110 |
111 | d. Affirmer understands and acknowledges that Creative Commons is not a
112 | party to this document and has no duty or obligation with respect to this
113 | CC0 or use of the Work.
114 |
115 | For more information, please see
116 |
117 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Introduction
2 | This library provides simple wrappers around some OpenGL 4 functionality. It was created for the [Advanced GPU Programming](http://cs.brown.edu/courses/cs195v/) course at Brown University.
3 |
4 | ## Example
5 |
6 | #include
7 | #include "gl4.h"
8 |
9 | Shader shader;
10 | Buffer quad;
11 | VAO layout;
12 |
13 | void setup() {
14 | shader.vertexShader(glsl(
15 | in vec2 vertex;
16 | out vec2 coord;
17 | void main() {
18 | coord = vertex * 0.5 + 0.5;
19 | gl_Position = vec4(vertex, 0.0, 1.0);
20 | }
21 | )).fragmentShader(glsl(
22 | in vec2 coord;
23 | out vec4 color;
24 | void main() {
25 | color = vec4(coord, 0.0, 1.0);
26 | }
27 | )).link();
28 |
29 | quad << vec2(-1, -1) << vec2(1, -1) << vec2(-1, 1) << vec2(1, 1);
30 | quad.upload();
31 | layout.create(shader, quad).attribute("vertex", 2).check();
32 | }
33 |
34 | void draw() {
35 | glClear(GL_COLOR_BUFFER_BIT);
36 | shader.use();
37 | layout.draw(GL_TRIANGLE_STRIP);
38 | shader.unuse();
39 | glutSwapBuffers();
40 | }
41 |
42 | int main(int argc, char *argv[]) {
43 | glutInit(&argc, argv);
44 | glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB);
45 | glutCreateWindow("Example");
46 | glutReshapeWindow(800, 600);
47 | glutDisplayFunc(draw);
48 | setup();
49 | glutMainLoop();
50 | return 0;
51 | }
52 |
--------------------------------------------------------------------------------
/gl4.cpp:
--------------------------------------------------------------------------------
1 | #include "gl4.h"
2 |
3 | mat4 &mat4::transpose() {
4 | std::swap(m01, m10); std::swap(m02, m20); std::swap(m03, m30);
5 | std::swap(m12, m21); std::swap(m13, m31); std::swap(m23, m32);
6 | return *this;
7 | }
8 |
9 | mat4 &mat4::rotateX(float degrees) {
10 | float radians = degrees * (M_PI / 180);
11 | float s = sinf(radians), c = cosf(radians);
12 | float t01 = m01, t02 = m02;
13 | float t11 = m11, t12 = m12;
14 | float t21 = m21, t22 = m22;
15 | float t31 = m31, t32 = m32;
16 | m01 = c * t01 - s * t02;
17 | m02 = c * t02 + s * t01;
18 | m11 = c * t11 - s * t12;
19 | m12 = c * t12 + s * t11;
20 | m21 = c * t21 - s * t22;
21 | m22 = c * t22 + s * t21;
22 | m31 = c * t31 - s * t32;
23 | m32 = c * t32 + s * t31;
24 | return *this;
25 | }
26 |
27 | mat4 &mat4::rotateY(float degrees) {
28 | float radians = degrees * (M_PI / 180);
29 | float s = sinf(radians), c = cosf(radians);
30 | float t02 = m02, t00 = m00;
31 | float t12 = m12, t10 = m10;
32 | float t22 = m22, t20 = m20;
33 | float t32 = m32, t30 = m30;
34 | m02 = c * t02 - s * t00;
35 | m00 = c * t00 + s * t02;
36 | m12 = c * t12 - s * t10;
37 | m10 = c * t10 + s * t12;
38 | m22 = c * t22 - s * t20;
39 | m20 = c * t20 + s * t22;
40 | m32 = c * t32 - s * t30;
41 | m30 = c * t30 + s * t32;
42 | return *this;
43 | }
44 |
45 | mat4 &mat4::rotateZ(float degrees) {
46 | float radians = degrees * (M_PI / 180);
47 | float s = sinf(radians), c = cosf(radians);
48 | float t00 = m00, t01 = m01;
49 | float t10 = m10, t11 = m11;
50 | float t20 = m20, t21 = m21;
51 | float t30 = m30, t31 = m31;
52 | m00 = c * t00 - s * t01;
53 | m01 = c * t01 + s * t00;
54 | m10 = c * t10 - s * t11;
55 | m11 = c * t11 + s * t10;
56 | m20 = c * t20 - s * t21;
57 | m21 = c * t21 + s * t20;
58 | m30 = c * t30 - s * t31;
59 | m31 = c * t31 + s * t30;
60 | return *this;
61 | }
62 |
63 | mat4 &mat4::rotate(float degrees, float x, float y, float z) {
64 | float radians = degrees * (M_PI / 180);
65 | float d = sqrtf(x*x + y*y + z*z);
66 | float s = sinf(radians), c = cosf(radians), t = 1 - c;
67 | x /= d; y /= d; z /= d;
68 | return *this *= mat4(
69 | x*x*t + c, x*y*t - z*s, x*z*t + y*s, 0,
70 | y*x*t + z*s, y*y*t + c, y*z*t - x*s, 0,
71 | z*x*t - y*s, z*y*t + x*s, z*z*t + c, 0,
72 | 0, 0, 0, 1);
73 | }
74 |
75 | mat4 &mat4::scale(float x, float y, float z) {
76 | m00 *= x; m01 *= y; m02 *= z;
77 | m10 *= x; m11 *= y; m12 *= z;
78 | m20 *= x; m21 *= y; m22 *= z;
79 | m30 *= x; m31 *= y; m32 *= z;
80 | return *this;
81 | }
82 |
83 | mat4 &mat4::translate(float x, float y, float z) {
84 | m03 += m00 * x + m01 * y + m02 * z;
85 | m13 += m10 * x + m11 * y + m12 * z;
86 | m23 += m20 * x + m21 * y + m22 * z;
87 | m33 += m30 * x + m31 * y + m32 * z;
88 | return *this;
89 | }
90 |
91 | mat4 &mat4::ortho(float l, float r, float b, float t, float n, float f) {
92 | return *this *= mat4(
93 | 2 / (r - l), 0, 0, (r + l) / (l - r),
94 | 0, 2 / (t - b), 0, (t + b) / (b - t),
95 | 0, 0, 2 / (n - f), (f + n) / (n - f),
96 | 0, 0, 0, 1);
97 | }
98 |
99 | mat4 &mat4::frustum(float l, float r, float b, float t, float n, float f) {
100 | return *this *= mat4(
101 | 2 * n / (r - l), 0, (r + l) / (r - l), 0,
102 | 0, 2 * n / (t - b), (t + b) / (t - b), 0,
103 | 0, 0, (f + n) / (n - f), 2 * f * n / (n - f),
104 | 0, 0, -1, 0);
105 | }
106 |
107 | mat4 &mat4::perspective(float fov, float aspect, float near, float far) {
108 | float y = tanf(fov * M_PI / 360) * near, x = y * aspect;
109 | return frustum(-x, x, -y, y, near, far);
110 | }
111 |
112 | mat4 &mat4::invert() {
113 | float t00 = m00, t01 = m01, t02 = m02, t03 = m03;
114 | *this = mat4(
115 | m11*m22*m33 - m11*m32*m23 - m12*m21*m33 + m12*m31*m23 + m13*m21*m32 - m13*m31*m22,
116 | -m01*m22*m33 + m01*m32*m23 + m02*m21*m33 - m02*m31*m23 - m03*m21*m32 + m03*m31*m22,
117 | m01*m12*m33 - m01*m32*m13 - m02*m11*m33 + m02*m31*m13 + m03*m11*m32 - m03*m31*m12,
118 | -m01*m12*m23 + m01*m22*m13 + m02*m11*m23 - m02*m21*m13 - m03*m11*m22 + m03*m21*m12,
119 |
120 | -m10*m22*m33 + m10*m32*m23 + m12*m20*m33 - m12*m30*m23 - m13*m20*m32 + m13*m30*m22,
121 | m00*m22*m33 - m00*m32*m23 - m02*m20*m33 + m02*m30*m23 + m03*m20*m32 - m03*m30*m22,
122 | -m00*m12*m33 + m00*m32*m13 + m02*m10*m33 - m02*m30*m13 - m03*m10*m32 + m03*m30*m12,
123 | m00*m12*m23 - m00*m22*m13 - m02*m10*m23 + m02*m20*m13 + m03*m10*m22 - m03*m20*m12,
124 |
125 | m10*m21*m33 - m10*m31*m23 - m11*m20*m33 + m11*m30*m23 + m13*m20*m31 - m13*m30*m21,
126 | -m00*m21*m33 + m00*m31*m23 + m01*m20*m33 - m01*m30*m23 - m03*m20*m31 + m03*m30*m21,
127 | m00*m11*m33 - m00*m31*m13 - m01*m10*m33 + m01*m30*m13 + m03*m10*m31 - m03*m30*m11,
128 | -m00*m11*m23 + m00*m21*m13 + m01*m10*m23 - m01*m20*m13 - m03*m10*m21 + m03*m20*m11,
129 |
130 | -m10*m21*m32 + m10*m31*m22 + m11*m20*m32 - m11*m30*m22 - m12*m20*m31 + m12*m30*m21,
131 | m00*m21*m32 - m00*m31*m22 - m01*m20*m32 + m01*m30*m22 + m02*m20*m31 - m02*m30*m21,
132 | -m00*m11*m32 + m00*m31*m12 + m01*m10*m32 - m01*m30*m12 - m02*m10*m31 + m02*m30*m11,
133 | m00*m11*m22 - m00*m21*m12 - m01*m10*m22 + m01*m20*m12 + m02*m10*m21 - m02*m20*m11
134 | );
135 | float det = m00 * t00 + m10 * t01 + m20 * t02 + m30 * t03;
136 | for (int i = 0; i < 16; i++) m[i] /= det;
137 | return *this;
138 | }
139 |
140 | mat4 &mat4::operator *= (const mat4 &t) {
141 | *this = mat4(
142 | m00*t.m00 + m01*t.m10 + m02*t.m20 + m03*t.m30,
143 | m00*t.m01 + m01*t.m11 + m02*t.m21 + m03*t.m31,
144 | m00*t.m02 + m01*t.m12 + m02*t.m22 + m03*t.m32,
145 | m00*t.m03 + m01*t.m13 + m02*t.m23 + m03*t.m33,
146 |
147 | m10*t.m00 + m11*t.m10 + m12*t.m20 + m13*t.m30,
148 | m10*t.m01 + m11*t.m11 + m12*t.m21 + m13*t.m31,
149 | m10*t.m02 + m11*t.m12 + m12*t.m22 + m13*t.m32,
150 | m10*t.m03 + m11*t.m13 + m12*t.m23 + m13*t.m33,
151 |
152 | m20*t.m00 + m21*t.m10 + m22*t.m20 + m23*t.m30,
153 | m20*t.m01 + m21*t.m11 + m22*t.m21 + m23*t.m31,
154 | m20*t.m02 + m21*t.m12 + m22*t.m22 + m23*t.m32,
155 | m20*t.m03 + m21*t.m13 + m22*t.m23 + m23*t.m33,
156 |
157 | m30*t.m00 + m31*t.m10 + m32*t.m20 + m33*t.m30,
158 | m30*t.m01 + m31*t.m11 + m32*t.m21 + m33*t.m31,
159 | m30*t.m02 + m31*t.m12 + m32*t.m22 + m33*t.m32,
160 | m30*t.m03 + m31*t.m13 + m32*t.m23 + m33*t.m33
161 | );
162 | return *this;
163 | }
164 |
165 | vec4 mat4::operator * (const vec4 &v) {
166 | return vec4(
167 | m00*v.x + m01*v.y + m02*v.z + m03*v.w,
168 | m10*v.x + m11*v.y + m12*v.z + m13*v.w,
169 | m20*v.x + m21*v.y + m22*v.z + m23*v.w,
170 | m30*v.x + m31*v.y + m32*v.z + m33*v.w
171 | );
172 | }
173 |
174 | vec4 operator * (const vec4 &v, const mat4 &t) {
175 | return vec4(
176 | t.m00*v.x + t.m10*v.y + t.m20*v.z + t.m30*v.w,
177 | t.m01*v.x + t.m11*v.y + t.m21*v.z + t.m31*v.w,
178 | t.m02*v.x + t.m12*v.y + t.m22*v.z + t.m32*v.w,
179 | t.m03*v.x + t.m13*v.y + t.m23*v.z + t.m33*v.w
180 | );
181 | }
182 |
183 | std::ostream &operator << (std::ostream &out, const mat4 &t) {
184 | return out << "mat4("
185 | << t.m00 << ", " << t.m01 << ", " << t.m02 << ", " << t.m03 << ",\n "
186 | << t.m10 << ", " << t.m11 << ", " << t.m12 << ", " << t.m13 << ",\n "
187 | << t.m20 << ", " << t.m21 << ", " << t.m22 << ", " << t.m23 << ",\n "
188 | << t.m30 << ", " << t.m31 << ", " << t.m32 << ", " << t.m33 << ")";
189 | }
190 |
191 | Texture &Texture::create(int w, int h, int d, int internalFormat, int format, int type, int filter, int wrap, void *data) {
192 | target = (d == 1) ? GL_TEXTURE_2D : GL_TEXTURE_3D;
193 | width = w;
194 | height = h;
195 | depth = d;
196 | if (!id) glGenTextures(1, &id);
197 | bind();
198 | glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
199 | glTexParameteri(target, GL_TEXTURE_MAG_FILTER, filter);
200 | glTexParameteri(target, GL_TEXTURE_MIN_FILTER, filter);
201 | glTexParameteri(target, GL_TEXTURE_WRAP_S, wrap);
202 | glTexParameteri(target, GL_TEXTURE_WRAP_T, wrap);
203 | if (target == GL_TEXTURE_2D) {
204 | glTexImage2D(target, 0, internalFormat, w, h, 0, format, type, data);
205 | } else {
206 | glTexParameteri(target, GL_TEXTURE_WRAP_R, wrap);
207 | glTexImage3D(target, 0, internalFormat, w, h, d, 0, format, type, data);
208 | }
209 | unbind();
210 | return *this;
211 | }
212 |
213 | void Texture::swapWith(Texture &other) {
214 | std::swap(id, other.id);
215 | std::swap(target, other.target);
216 | std::swap(width, other.width);
217 | std::swap(height, other.height);
218 | std::swap(depth, other.depth);
219 | }
220 |
221 | void FBO::bind() {
222 | glBindFramebuffer(GL_FRAMEBUFFER, id);
223 | if (resizeViewport) {
224 | glGetIntegerv(GL_VIEWPORT, oldViewport);
225 | glViewport(newViewport[0], newViewport[1], newViewport[2], newViewport[3]);
226 | }
227 | }
228 |
229 | void FBO::unbind() {
230 | glBindFramebuffer(GL_FRAMEBUFFER, 0);
231 | if (resizeViewport) {
232 | glViewport(oldViewport[0], oldViewport[1], oldViewport[2], oldViewport[3]);
233 | }
234 | }
235 |
236 | FBO &FBO::attachColor(const Texture &texture, unsigned int attachment, unsigned int layer) {
237 | newViewport[2] = texture.width;
238 | newViewport[3] = texture.height;
239 | if (!id) glGenFramebuffers(1, &id);
240 | bind();
241 |
242 | // Bind a 2D texture (using a 2D layer of a 3D texture)
243 | if (texture.target == GL_TEXTURE_2D) {
244 | glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachment, texture.target, texture.id, 0);
245 | } else {
246 | glFramebufferTexture3D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + attachment, texture.target, texture.id, 0, layer);
247 | }
248 |
249 | // Need to call glDrawBuffers() for OpenGL to draw to multiple attachments
250 | if (attachment >= drawBuffers.size()) drawBuffers.resize(attachment + 1, GL_NONE);
251 | drawBuffers[attachment] = GL_COLOR_ATTACHMENT0 + attachment;
252 | glDrawBuffers(drawBuffers.size(), drawBuffers.data());
253 |
254 | unbind();
255 | return *this;
256 | }
257 |
258 | FBO &FBO::detachColor(unsigned int attachment) {
259 | bind();
260 |
261 | // Update the draw buffers
262 | if (attachment < drawBuffers.size()) {
263 | drawBuffers[attachment] = GL_NONE;
264 | glDrawBuffers(drawBuffers.size(), drawBuffers.data());
265 | }
266 |
267 | unbind();
268 | return *this;
269 | }
270 |
271 | FBO &FBO::check() {
272 | bind();
273 | if (autoDepth) {
274 | if (renderbufferWidth != newViewport[2] || renderbufferHeight != newViewport[3]) {
275 | renderbufferWidth = newViewport[2];
276 | renderbufferHeight = newViewport[3];
277 | if (!renderbuffer) glGenRenderbuffers(1, &renderbuffer);
278 | glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer);
279 | glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT32, renderbufferWidth, renderbufferHeight);
280 | glBindRenderbuffer(GL_RENDERBUFFER, 0);
281 | }
282 | glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, renderbuffer);
283 | }
284 | switch (glCheckFramebufferStatus(GL_FRAMEBUFFER)) {
285 | case GL_FRAMEBUFFER_COMPLETE: break;
286 | case GL_FRAMEBUFFER_UNDEFINED: printf("GL_FRAMEBUFFER_UNDEFINED\n"); exit(0);
287 | case GL_FRAMEBUFFER_UNSUPPORTED: printf("GL_FRAMEBUFFER_UNSUPPORTED\n"); exit(0);
288 | case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT: printf("GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT\n"); exit(0);
289 | case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER: printf("GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER\n"); exit(0);
290 | case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER: printf("GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER\n"); exit(0);
291 | case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE: printf("GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE\n"); exit(0);
292 | case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT: printf("GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT\n"); exit(0);
293 | case GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS: printf("GL_FRAMEBUFFER_INCOMPLETE_LAYER_TARGETS\n"); exit(0);
294 | case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT: printf("GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT\n"); exit(0);
295 | case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT: printf("GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT\n"); exit(0);
296 | default: printf("Unknown glCheckFramebufferStatus error"); exit(0);
297 | }
298 | unbind();
299 | return *this;
300 | }
301 |
302 | Shader::~Shader() {
303 | glDeleteProgram(id);
304 | for (size_t i = 0; i < stages.size(); i++) {
305 | glDeleteShader(stages[i]);
306 | }
307 | }
308 |
309 | static void error(const char *type, const char *error, const char *source = NULL) {
310 | printf("----- %s -----\n", type);
311 | printf("%s\n", error);
312 | if (source) {
313 | printf("----- source code -----\n");
314 | printf("%s\n", source);
315 | }
316 | exit(0);
317 | }
318 |
319 | Shader &Shader::shader(int type, const char *source) {
320 | // Compile shader
321 | unsigned int shader = glCreateShader(type);
322 | glShaderSource(shader, 1, &source, NULL);
323 | glCompileShader(shader);
324 | stages.push_back(shader);
325 |
326 | // Check for errors
327 | char buffer[512];
328 | int length;
329 | glGetShaderInfoLog(shader, sizeof(buffer), &length, buffer);
330 | if (length) error("compile error", buffer, source);
331 |
332 | // Allow chaining
333 | return *this;
334 | }
335 |
336 | void Shader::link() {
337 | // Create and link program
338 | if (!id) id = glCreateProgram();
339 | for (size_t i = 0; i < stages.size(); i++) {
340 | glAttachShader(id, stages[i]);
341 | }
342 | glLinkProgram(id);
343 |
344 | // Check for errors
345 | char buffer[512];
346 | int length;
347 | glGetProgramInfoLog(id, sizeof(buffer), &length, buffer);
348 | if (length) error("link error", buffer);
349 | }
350 |
351 | void VAO::check() {
352 | if (vertices && vertices->currentTarget() != GL_ARRAY_BUFFER) {
353 | printf("expected vertices to have GL_ARRAY_BUFFER, got 0x%04X\n", vertices->currentTarget());
354 | exit(0);
355 | }
356 | if (indices && indices->currentTarget() != GL_ELEMENT_ARRAY_BUFFER) {
357 | printf("expected indices to have GL_ELEMENT_ARRAY_BUFFER, got 0x%04X\n", indices->currentTarget());
358 | exit(0);
359 | }
360 | if (offset != stride) {
361 | printf("expected size of attributes (%d bytes) to add up to size of vertex (%d bytes)\n", offset, stride);
362 | exit(0);
363 | }
364 | }
365 |
--------------------------------------------------------------------------------
/gl4.h:
--------------------------------------------------------------------------------
1 | #ifndef GL4_H
2 | #define GL4_H
3 |
4 | // This is a small library to make OpenGL 4 easier. All objects are lazily
5 | // initialized so it is safe to declare them at global scope (if this wasn't
6 | // the case, they would fail to be created as the OpenGL context wouldn't exist
7 | // yet). The provided vector libraries attempt to imitate some of GLSL syntax.
8 | // The wrappers are intentionally "leaky" in that they don't use private
9 | // variables so you can implement functionality that they don't have if needed.
10 |
11 | #include
12 | #include
13 | #include
14 | #include
15 | #include
16 | #include
17 | #include
18 |
19 | // Definitions for new macros in case they aren't defined.
20 | #define GL_R32F 0x822E
21 | #define GL_RG32F 0x8230
22 | #define GL_RGB32F 0x8815
23 | #define GL_RGBA32F 0x8814
24 | #define GL_PATCHES 0x000E
25 | #define GL_PATCH_VERTICES 0x8E72
26 | #define GL_TESS_CONTROL_SHADER 0x8E88
27 | #define GL_TESS_EVALUATION_SHADER 0x8E87
28 |
29 | // Forward declarations for new functions in case they aren't defined.
30 | extern "C" {
31 | void glUseProgram(GLuint program);
32 | void glGenFramebuffers(GLsizei n, GLuint *ids);
33 | void glGenRenderbuffers(GLsizei n, GLuint *ids);
34 | void glBindFramebuffer(GLenum target, GLuint framebuffer);
35 | void glBindRenderbuffer(GLenum target, GLuint renderbuffer);
36 | void glRenderbufferStorage(GLenum target, GLenum internalformat, GLsizei width, GLsizei height);
37 | void glFramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer);
38 | void glFramebufferTexture2D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level);
39 | void glFramebufferTexture3D(GLenum target, GLenum attachment, GLenum textarget, GLuint texture, GLint level, GLint layer);
40 | void glDeleteProgram(GLuint program);
41 | void glDeleteShader(GLuint shader);
42 | void glShaderSource(GLuint shader, GLsizei count, const GLchar **string, const GLint *length);
43 | void glAttachShader(GLuint program, GLuint shader);
44 | void glLinkProgram(GLuint program);
45 | void glCompileShader(GLuint shader);
46 | void glGetShaderInfoLog(GLuint shader, GLsizei maxLength, GLsizei *length, GLchar *infoLog);
47 | void glGetProgramInfoLog(GLuint program, GLsizei maxLength, GLsizei *length, GLchar *infoLog);
48 | void glGenBuffers(GLsizei n, GLuint *buffers);
49 | void glDeleteBuffers(GLsizei n, const GLuint *buffers);
50 | void glBindBuffer(GLenum target, GLuint buffer);
51 | void glBufferData(GLenum target, GLsizeiptr size, const GLvoid *data, GLenum usage);
52 | void glGenVertexArrays(GLsizei n, GLuint *arrays);
53 | void glDeleteVertexArrays(GLsizei n, const GLuint *arrays);
54 | void glBindVertexArray(GLuint array);
55 | void glDrawArraysInstanced(GLenum mode, GLint first, GLsizei count, GLsizei primcount);
56 | void glDrawElementsInstanced(GLenum mode, GLsizei count, GLenum type, const void *indices, GLsizei primcount);
57 | void glEnableVertexAttribArray(GLuint index);
58 | void glDisableVertexAttribArray(GLuint index);
59 | void glBindAttribLocation(GLuint program, GLuint index, const GLchar *name);
60 | void glVertexAttribPointer(GLuint index, GLint size, GLenum type, GLboolean normalized, GLsizei stride, const GLvoid *pointer);
61 | void glDeleteFramebuffers(GLsizei n, GLuint *framebuffers);
62 | void glDeleteRenderbuffers(GLsizei n, GLuint *renderbuffers);
63 | void glDrawBuffers(GLsizei n, const GLenum *bufs);
64 | void glUniform1i(GLint location, GLint v0);
65 | void glUniform2i(GLint location, GLint v0, GLint v1);
66 | void glUniform3i(GLint location, GLint v0, GLint v1, GLint v2);
67 | void glUniform4i(GLint location, GLint v0, GLint v1, GLint v2, GLint v3);
68 | void glUniform1f(GLint location, GLfloat v0);
69 | void glUniform2f(GLint location, GLfloat v0, GLfloat v1);
70 | void glUniform3f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2);
71 | void glUniform4f(GLint location, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3);
72 | void glUniform1fv(GLint location, GLsizei count, const GLfloat *value);
73 | void glUniform2fv(GLint location, GLsizei count, const GLfloat *value);
74 | void glUniform3fv(GLint location, GLsizei count, const GLfloat *value);
75 | void glUniform4fv(GLint location, GLsizei count, const GLfloat *value);
76 | void glUniform1iv(GLint location, GLsizei count, const GLint *value);
77 | void glUniform2iv(GLint location, GLsizei count, const GLint *value);
78 | void glUniform3iv(GLint location, GLsizei count, const GLint *value);
79 | void glUniform4iv(GLint location, GLsizei count, const GLint *value);
80 | void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
81 | void glPatchParameteri(GLenum pname, GLint value);
82 | void glPatchParameterfv(GLenum pname, const GLfloat *values);
83 | GLuint glCreateShader(GLenum shaderType);
84 | GLuint glCreateProgram();
85 | GLuint glCheckFramebufferStatus(GLenum target);
86 | GLuint glGetAttribLocation(GLuint program, const GLchar *name);
87 | GLuint glGetUniformLocation(GLuint program, const GLchar *name);
88 | }
89 |
90 | struct vec2 {
91 | union {
92 | struct { float x, y; };
93 | struct { float s, t; };
94 | struct { float r, g; };
95 | float xy[2];
96 | float st[2];
97 | float rg[2];
98 | };
99 |
100 | vec2() : x(), y() {}
101 | vec2(float x, float y) : x(x), y(y) {}
102 | vec2(const vec2 &xy) : x(xy.x), y(xy.y) {}
103 | explicit vec2(float f) : x(f), y(f) {}
104 |
105 | vec2 operator + () const { return vec2(+x, +y); }
106 | vec2 operator - () const { return vec2(-x, -y); }
107 |
108 | vec2 operator + (const vec2 &vec) const { return vec2(x + vec.x, y + vec.y); }
109 | vec2 operator - (const vec2 &vec) const { return vec2(x - vec.x, y - vec.y); }
110 | vec2 operator * (const vec2 &vec) const { return vec2(x * vec.x, y * vec.y); }
111 | vec2 operator / (const vec2 &vec) const { return vec2(x / vec.x, y / vec.y); }
112 | vec2 operator + (float s) const { return vec2(x + s, y + s); }
113 | vec2 operator - (float s) const { return vec2(x - s, y - s); }
114 | vec2 operator * (float s) const { return vec2(x * s, y * s); }
115 | vec2 operator / (float s) const { return vec2(x / s, y / s); }
116 |
117 | friend vec2 operator + (float s, const vec2 &vec) { return vec2(s + vec.x, s + vec.y); }
118 | friend vec2 operator - (float s, const vec2 &vec) { return vec2(s - vec.x, s - vec.y); }
119 | friend vec2 operator * (float s, const vec2 &vec) { return vec2(s * vec.x, s * vec.y); }
120 | friend vec2 operator / (float s, const vec2 &vec) { return vec2(s / vec.x, s / vec.y); }
121 |
122 | vec2 &operator += (const vec2 &vec) { return *this = *this + vec; }
123 | vec2 &operator -= (const vec2 &vec) { return *this = *this - vec; }
124 | vec2 &operator *= (const vec2 &vec) { return *this = *this * vec; }
125 | vec2 &operator /= (const vec2 &vec) { return *this = *this / vec; }
126 | vec2 &operator += (float s) { return *this = *this + s; }
127 | vec2 &operator -= (float s) { return *this = *this - s; }
128 | vec2 &operator *= (float s) { return *this = *this * s; }
129 | vec2 &operator /= (float s) { return *this = *this / s; }
130 |
131 | bool operator == (const vec2 &vec) const { return x == vec.x && y == vec.y; }
132 | bool operator != (const vec2 &vec) const { return x != vec.x || y != vec.y; }
133 |
134 | friend float length(const vec2 &v) { return sqrtf(v.x * v.x + v.y * v.y); }
135 | friend float dot(const vec2 &a, const vec2 &b) { return a.x * b.x + a.y * b.y; }
136 | friend float max(const vec2 &v) { return fmaxf(v.x, v.y); }
137 | friend float min(const vec2 &v) { return fminf(v.x, v.y); }
138 | friend vec2 max(const vec2 &a, const vec2 &b) { return vec2(fmaxf(a.x, b.x), fmaxf(a.y, b.y)); }
139 | friend vec2 min(const vec2 &a, const vec2 &b) { return vec2(fminf(a.x, b.x), fminf(a.y, b.y)); }
140 | friend vec2 floor(const vec2 &v) { return vec2(floorf(v.x), floorf(v.y)); }
141 | friend vec2 ceil(const vec2 &v) { return vec2(ceilf(v.x), ceilf(v.y)); }
142 | friend vec2 abs(const vec2 &v) { return vec2(fabsf(v.x), fabsf(v.y)); }
143 | friend vec2 fract(const vec2 &v) { return v - floor(v); }
144 | friend vec2 normalized(const vec2 &v) { return v / length(v); }
145 |
146 | friend std::ostream &operator << (std::ostream &out, const vec2 &v) {
147 | return out << "vec2(" << v.x << ", " << v.y << ")";
148 | }
149 | };
150 |
151 | struct vec3 {
152 | union {
153 | struct { float x, y, z; };
154 | struct { float s, t, p; };
155 | struct { float r, g, b; };
156 | float xyz[3];
157 | float stp[3];
158 | float rgb[3];
159 | };
160 |
161 | vec3() : x(), y(), z() {}
162 | vec3(float x, float y, float z) : x(x), y(y), z(z) {}
163 | vec3(const vec2 &xy, float z) : x(xy.x), y(xy.y), z(z) {}
164 | vec3(float x, const vec2 &yz) : x(x), y(yz.x), z(yz.y) {}
165 | vec3(const vec3 &xyz) : x(xyz.x), y(xyz.y), z(xyz.z) {}
166 | explicit vec3(float f) : x(f), y(f), z(f) {}
167 |
168 | vec3 operator + () const { return vec3(+x, +y, +z); }
169 | vec3 operator - () const { return vec3(-x, -y, -z); }
170 |
171 | vec3 operator + (const vec3 &vec) const { return vec3(x + vec.x, y + vec.y, z + vec.z); }
172 | vec3 operator - (const vec3 &vec) const { return vec3(x - vec.x, y - vec.y, z - vec.z); }
173 | vec3 operator * (const vec3 &vec) const { return vec3(x * vec.x, y * vec.y, z * vec.z); }
174 | vec3 operator / (const vec3 &vec) const { return vec3(x / vec.x, y / vec.y, z / vec.z); }
175 | vec3 operator + (float s) const { return vec3(x + s, y + s, z + s); }
176 | vec3 operator - (float s) const { return vec3(x - s, y - s, z - s); }
177 | vec3 operator * (float s) const { return vec3(x * s, y * s, z * s); }
178 | vec3 operator / (float s) const { return vec3(x / s, y / s, z / s); }
179 |
180 | friend vec3 operator + (float s, const vec3 &vec) { return vec3(s + vec.x, s + vec.y, s + vec.z); }
181 | friend vec3 operator - (float s, const vec3 &vec) { return vec3(s - vec.x, s - vec.y, s - vec.z); }
182 | friend vec3 operator * (float s, const vec3 &vec) { return vec3(s * vec.x, s * vec.y, s * vec.z); }
183 | friend vec3 operator / (float s, const vec3 &vec) { return vec3(s / vec.x, s / vec.y, s / vec.z); }
184 |
185 | vec3 &operator += (const vec3 &vec) { return *this = *this + vec; }
186 | vec3 &operator -= (const vec3 &vec) { return *this = *this - vec; }
187 | vec3 &operator *= (const vec3 &vec) { return *this = *this * vec; }
188 | vec3 &operator /= (const vec3 &vec) { return *this = *this / vec; }
189 | vec3 &operator += (float s) { return *this = *this + s; }
190 | vec3 &operator -= (float s) { return *this = *this - s; }
191 | vec3 &operator *= (float s) { return *this = *this * s; }
192 | vec3 &operator /= (float s) { return *this = *this / s; }
193 |
194 | bool operator == (const vec3 &vec) const { return x == vec.x && y == vec.y && z == vec.z; }
195 | bool operator != (const vec3 &vec) const { return x != vec.x || y != vec.y || z != vec.z; }
196 |
197 | friend float length(const vec3 &v) { return sqrtf(v.x * v.x + v.y * v.y + v.z * v.z); }
198 | friend float dot(const vec3 &a, const vec3 &b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
199 | friend float max(const vec3 &v) { return fmaxf(fmaxf(v.x, v.y), v.z); }
200 | friend float min(const vec3 &v) { return fminf(fminf(v.x, v.y), v.z); }
201 | friend vec3 max(const vec3 &a, const vec3 &b) { return vec3(fmaxf(a.x, b.x), fmaxf(a.y, b.y), fmaxf(a.z, b.z)); }
202 | friend vec3 min(const vec3 &a, const vec3 &b) { return vec3(fminf(a.x, b.x), fminf(a.y, b.y), fminf(a.z, b.z)); }
203 | friend vec3 floor(const vec3 &v) { return vec3(floorf(v.x), floorf(v.y), floorf(v.z)); }
204 | friend vec3 ceil(const vec3 &v) { return vec3(ceilf(v.x), ceilf(v.y), ceilf(v.z)); }
205 | friend vec3 abs(const vec3 &v) { return vec3(fabsf(v.x), fabsf(v.y), fabsf(v.z)); }
206 | friend vec3 fract(const vec3 &v) { return v - floor(v); }
207 | friend vec3 normalized(const vec3 &v) { return v / length(v); }
208 | friend vec3 cross(const vec3 &a, const vec3 &b) { return vec3(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); }
209 |
210 | friend std::ostream &operator << (std::ostream &out, const vec3 &v) {
211 | return out << "vec3(" << v.x << ", " << v.y << ", " << v.z << ")";
212 | }
213 | };
214 |
215 | struct vec4 {
216 | union {
217 | struct { float x, y, z, w; };
218 | struct { float s, t, p, q; };
219 | struct { float r, g, b, a; };
220 | float xyzw[4];
221 | float stpq[4];
222 | float rgba[4];
223 | };
224 |
225 | vec4() : x(), y(), z(), w() {}
226 | vec4(float x, float y, float z, float w) : x(x), y(y), z(z), w(w) {}
227 | vec4(const vec2 &xy, float z, float w) : x(xy.x), y(xy.y), z(z), w(w) {}
228 | vec4(float x, const vec2 &yz, float w) : x(x), y(yz.x), z(yz.y), w(w) {}
229 | vec4(float x, float y, const vec2 &zw) : x(x), y(y), z(zw.x), w(zw.y) {}
230 | vec4(const vec2 &xy, const vec2 &zw) : x(xy.x), y(xy.y), z(zw.x), w(zw.y) {}
231 | vec4(const vec3 &xyz, float w) : x(xyz.x), y(xyz.y), z(xyz.z), w(w) {}
232 | vec4(float x, const vec3 &yzw) : x(x), y(yzw.x), z(yzw.y), w(yzw.z) {}
233 | vec4(const vec4 &xyzw) : x(xyzw.x), y(xyzw.y), z(xyzw.z), w(xyzw.w) {}
234 | explicit vec4(float f) : x(f), y(f), z(f), w(f) {}
235 |
236 | vec4 operator + () const { return vec4(+x, +y, +z, +w); }
237 | vec4 operator - () const { return vec4(-x, -y, -z, -w); }
238 |
239 | vec4 operator + (const vec4 &vec) const { return vec4(x + vec.x, y + vec.y, z + vec.z, w + vec.w); }
240 | vec4 operator - (const vec4 &vec) const { return vec4(x - vec.x, y - vec.y, z - vec.z, w - vec.w); }
241 | vec4 operator * (const vec4 &vec) const { return vec4(x * vec.x, y * vec.y, z * vec.z, w * vec.w); }
242 | vec4 operator / (const vec4 &vec) const { return vec4(x / vec.x, y / vec.y, z / vec.z, w / vec.w); }
243 | vec4 operator + (float s) const { return vec4(x + s, y + s, z + s, w + s); }
244 | vec4 operator - (float s) const { return vec4(x - s, y - s, z - s, w - s); }
245 | vec4 operator * (float s) const { return vec4(x * s, y * s, z * s, w * s); }
246 | vec4 operator / (float s) const { return vec4(x / s, y / s, z / s, w / s); }
247 |
248 | friend vec4 operator + (float s, const vec4 &vec) { return vec4(s + vec.x, s + vec.y, s + vec.z, s + vec.w); }
249 | friend vec4 operator - (float s, const vec4 &vec) { return vec4(s - vec.x, s - vec.y, s - vec.z, s - vec.w); }
250 | friend vec4 operator * (float s, const vec4 &vec) { return vec4(s * vec.x, s * vec.y, s * vec.z, s * vec.w); }
251 | friend vec4 operator / (float s, const vec4 &vec) { return vec4(s / vec.x, s / vec.y, s / vec.z, s / vec.w); }
252 |
253 | vec4 &operator += (const vec4 &vec) { return *this = *this + vec; }
254 | vec4 &operator -= (const vec4 &vec) { return *this = *this - vec; }
255 | vec4 &operator *= (const vec4 &vec) { return *this = *this * vec; }
256 | vec4 &operator /= (const vec4 &vec) { return *this = *this / vec; }
257 | vec4 &operator += (float s) { return *this = *this + s; }
258 | vec4 &operator -= (float s) { return *this = *this - s; }
259 | vec4 &operator *= (float s) { return *this = *this * s; }
260 | vec4 &operator /= (float s) { return *this = *this / s; }
261 |
262 | bool operator == (const vec4 &vec) const { return x == vec.x && y == vec.y && z == vec.z && w == vec.w; }
263 | bool operator != (const vec4 &vec) const { return x != vec.x || y != vec.y || z != vec.z || w != vec.w; }
264 |
265 | friend float length(const vec4 &v) { return sqrtf(v.x * v.x + v.y * v.y + v.z * v.z + v.w * v.w); }
266 | friend float dot(const vec4 &a, const vec4 &b) { return a.x * b.x + a.y * b.y + a.z * b.z + a.w * a.w; }
267 | friend float max(const vec4 &v) { return fmaxf(fmaxf(v.x, v.y), fmaxf(v.z, v.w)); }
268 | friend float min(const vec4 &v) { return fminf(fminf(v.x, v.y), fminf(v.z, v.w)); }
269 | friend vec4 max(const vec4 &a, const vec4 &b) { return vec4(fmaxf(a.x, b.x), fmaxf(a.y, b.y), fmaxf(a.z, b.z), fmaxf(a.w, b.w)); }
270 | friend vec4 min(const vec4 &a, const vec4 &b) { return vec4(fminf(a.x, b.x), fminf(a.y, b.y), fminf(a.z, b.z), fminf(a.w, b.w)); }
271 | friend vec4 floor(const vec4 &v) { return vec4(floorf(v.x), floorf(v.y), floorf(v.z), floorf(v.w)); }
272 | friend vec4 ceil(const vec4 &v) { return vec4(ceilf(v.x), ceilf(v.y), ceilf(v.z), ceilf(v.w)); }
273 | friend vec4 abs(const vec4 &v) { return vec4(fabsf(v.x), fabsf(v.y), fabsf(v.z), fabsf(v.w)); }
274 | friend vec4 fract(const vec4 &v) { return v - floor(v); }
275 | friend vec4 normalized(const vec4 &v) { return v / length(v); }
276 |
277 | friend std::ostream &operator << (std::ostream &out, const vec4 &v) {
278 | return out << "vec4(" << v.x << ", " << v.y << ", " << v.z << ", " << v.w << ")";
279 | }
280 | };
281 |
282 | struct mat4 {
283 | union {
284 | struct {
285 | float m00, m01, m02, m03;
286 | float m10, m11, m12, m13;
287 | float m20, m21, m22, m23;
288 | float m30, m31, m32, m33;
289 | };
290 | float m[16];
291 | };
292 |
293 | mat4() :
294 | m00(1), m01(), m02(), m03(),
295 | m10(), m11(1), m12(), m13(),
296 | m20(), m21(), m22(1), m23(),
297 | m30(), m31(), m32(), m33(1) {}
298 | mat4(const vec4 &r0, const vec4 &r1, const vec4 &r2, const vec4 &r3) :
299 | m00(r0.x), m01(r0.y), m02(r0.z), m03(r0.w),
300 | m10(r1.x), m11(r1.y), m12(r1.z), m13(r1.w),
301 | m20(r2.x), m21(r2.y), m22(r2.z), m23(r2.w),
302 | m30(r3.x), m31(r3.y), m32(r3.z), m33(r3.w) {}
303 | mat4(
304 | float m00, float m01, float m02, float m03,
305 | float m10, float m11, float m12, float m13,
306 | float m20, float m21, float m22, float m23,
307 | float m30, float m31, float m32, float m33) :
308 | m00(m00), m01(m01), m02(m02), m03(m03),
309 | m10(m10), m11(m11), m12(m12), m13(m13),
310 | m20(m20), m21(m21), m22(m22), m23(m23),
311 | m30(m30), m31(m31), m32(m32), m33(m33) {}
312 |
313 | mat4 &transpose();
314 | mat4 &rotateX(float degrees);
315 | mat4 &rotateY(float degrees);
316 | mat4 &rotateZ(float degrees);
317 | mat4 &rotate(float degrees, float x, float y, float z);
318 | mat4 &rotate(float degrees, const vec3 &v) { return rotate(degrees, v.x, v.y, v.z); }
319 | mat4 &scale(float x, float y, float z);
320 | mat4 &scale(const vec3 &v) { return scale(v.x, v.y, v.z); }
321 | mat4 &translate(float x, float y, float z);
322 | mat4 &translate(const vec3 &v) { return translate(v.x, v.y, v.z); }
323 | mat4 &ortho(float l, float r, float b, float t, float n, float f);
324 | mat4 &frustum(float l, float r, float b, float t, float n, float f);
325 | mat4 &perspective(float fov, float aspect, float near, float far);
326 | mat4 &invert();
327 |
328 | mat4 &operator *= (const mat4 &t);
329 | vec4 operator * (const vec4 &v);
330 | friend vec4 operator * (const vec4 &v, const mat4 &t);
331 | friend std::ostream &operator << (std::ostream &out, const mat4 &t);
332 | };
333 |
334 | // Supports both 2D and 3D textures (2D textures are just textures with a depth
335 | // of 1). When rendering back and forth between two textures (ping-ponging), it
336 | // is easiest to just call swapWith() after rendering.
337 | struct Texture {
338 | unsigned int id;
339 | int target, width, height, depth;
340 |
341 | Texture() : id(), target(), width(), height(), depth() {}
342 | ~Texture() { glDeleteTextures(1, &id); }
343 |
344 | void bind(int unit = 0) const { glActiveTexture(GL_TEXTURE0 + unit); glBindTexture(target, id); }
345 | void unbind(int unit = 0) const { glActiveTexture(GL_TEXTURE0 + unit); glBindTexture(target, 0); }
346 |
347 | // Create a new texture. GL_TEXTURE_2D is used if depth == 1, otherwise
348 | // GL_TEXTURE_3D is used.
349 | Texture &create(int width, int height, int depth, int internalFormat, int format, int type, int filter, int wrap, void *data = NULL);
350 |
351 | // Swap the members of this texture with the members of other.
352 | void swapWith(Texture &other);
353 | };
354 |
355 | // A framebuffer object that can take color attachments. Draw calls between
356 | // bind() and unbind() are drawn to the attached textures.
357 | //
358 | // Usage:
359 | //
360 | // FBO fbo;
361 | //
362 | // fbo.attachColor(texture).check().bind();
363 | // // draw stuff
364 | // fbo.unbind();
365 | //
366 | struct FBO {
367 | unsigned int id;
368 | unsigned int renderbuffer;
369 | bool autoDepth;
370 | bool resizeViewport;
371 | int newViewport[4], oldViewport[4];
372 | int renderbufferWidth, renderbufferHeight;
373 | std::vector drawBuffers;
374 |
375 | FBO(bool autoDepth = true, bool resizeViewport = true) : id(), renderbuffer(), autoDepth(autoDepth),
376 | resizeViewport(resizeViewport), newViewport(), oldViewport(), renderbufferWidth(), renderbufferHeight() {}
377 | ~FBO() { glDeleteFramebuffers(1, &id); glDeleteRenderbuffers(1, &renderbuffer); }
378 |
379 | // Draw calls between these will be drawn to attachments. If resizeViewport
380 | // is true this will automatically resize the viewport to the size of the
381 | // last attached texture.
382 | void bind();
383 | void unbind();
384 |
385 | // Draw to texture 2D in the indicated attachment location (or a 2D layer of
386 | // a 3D texture).
387 | FBO &attachColor(const Texture &texture, unsigned int attachment = 0, unsigned int layer = 0);
388 |
389 | // Stop drawing to the indicated color attachment
390 | FBO &detachColor(unsigned int attachment = 0);
391 |
392 | // Call after all attachColor() calls, validates attachments.
393 | FBO &check();
394 | };
395 |
396 | // Use this macro to pass raw GLSL to Shader::shader()
397 | #define glsl(x) "#version 400\n" #x
398 |
399 | // Wraps a GLSL shader program and all attached shader stages. Meant to be used
400 | // with the glsl() macro.
401 | //
402 | // Usage:
403 | //
404 | // // Initialization
405 | // Shader shader;
406 | // shader.vertexShader(glsl(
407 | // in vec4 vertex;
408 | // void main() {
409 | // gl_Position = vertex;
410 | // }
411 | // )).fragmentShader(glsl(
412 | // out vec4 color;
413 | // void main() {
414 | // color = vec4(1.0, 0.0, 0.0, 1.0);
415 | // }
416 | // )).link();
417 | //
418 | // // Rendering
419 | // shader.use();
420 | // // Draw stuff
421 | // shader.unuse();
422 | //
423 | struct Shader {
424 | unsigned int id;
425 | std::vector stages;
426 |
427 | Shader() : id() {}
428 | ~Shader();
429 |
430 | Shader &shader(int type, const char *source);
431 | Shader &vertexShader(const char *source) { return shader(GL_VERTEX_SHADER, source); }
432 | Shader &fragmentShader(const char *source) { return shader(GL_FRAGMENT_SHADER, source); }
433 | Shader &geometryShader(const char *source) { return shader(GL_GEOMETRY_SHADER, source); }
434 | Shader &tessControlShader(const char *source) { return shader(GL_TESS_CONTROL_SHADER, source); }
435 | Shader &tessEvalShader(const char *source) { return shader(GL_TESS_EVALUATION_SHADER, source); }
436 |
437 | void link();
438 | void use() const { glUseProgram(id); }
439 | void unuse() const { glUseProgram(0); }
440 |
441 | unsigned int attribute(const char *name) const { return glGetAttribLocation(id, name); }
442 | unsigned int uniform(const char *name) const { return glGetUniformLocation(id, name); }
443 |
444 | void uniformInt(const char *name, int i) const { glUniform1i(uniform(name), i); }
445 | void uniformFloat(const char *name, float f) const { glUniform1f(uniform(name), f); }
446 | void uniform(const char *name, const vec2 &v) const { glUniform2fv(uniform(name), 1, v.xy); }
447 | void uniform(const char *name, const vec3 &v) const { glUniform3fv(uniform(name), 1, v.xyz); }
448 | void uniform(const char *name, const vec4 &v) const { glUniform4fv(uniform(name), 1, v.xyzw); }
449 | void uniform(const char *name, const mat4 &m) const { glUniformMatrix4fv(uniform(name), 1, true, m.m); }
450 | };
451 |
452 | // A vertex buffer containing a certain type. For example, the simplest vertex
453 | // buffer would be Buffer but more complex formats could use Buffer
454 | // with struct Vertex { vec3 position; vec2 coord; }. Each Buffer is meant to be
455 | // used with a VAO.
456 | //
457 | // Index buffers should be specified using an integer type directly, i.e.
458 | // Buffer, since that's what VAO expects.
459 | //
460 | // Usage:
461 | //
462 | // Buffer vertices;
463 | // vertices << vec3(1, 0, 0) << vec3(0, 1, 0) << vec3(0, 0, 1);
464 | // vertices.upload();
465 | //
466 | // Buffer indices;
467 | // indices << 0 << 1 << 2;
468 | // indices.upload(GL_ELEMENT_ARRAY_BUFFER);
469 | //
470 | template
471 | struct Buffer {
472 | std::vector data;
473 | unsigned int id;
474 | int currentTarget;
475 |
476 | Buffer() : id(), currentTarget() {}
477 | ~Buffer() {
478 | glDeleteBuffers(1, &id);
479 | }
480 |
481 | void bind() const { glBindBuffer(currentTarget, id); }
482 | void unbind() const { glBindBuffer(currentTarget, 0); }
483 |
484 | void upload(int target = GL_ARRAY_BUFFER, int usage = GL_STATIC_DRAW) {
485 | if (!id) glGenBuffers(1, &id);
486 | currentTarget = target;
487 | bind();
488 | glBufferData(currentTarget, data.size() * sizeof(T), data.data(), usage);
489 | unbind();
490 | }
491 |
492 | unsigned int size() const { return data.size(); }
493 | Buffer &operator << (const T &t) { data.push_back(t); return *this; }
494 | };
495 |
496 | // Convert a C++ type to an OpenGL type enum using TypeToOpenGL::value
497 | template struct TypeToOpenGL {};
498 | template <> struct TypeToOpenGL { enum { value = GL_BOOL }; };
499 | template <> struct TypeToOpenGL { enum { value = GL_FLOAT }; };
500 | template <> struct TypeToOpenGL { enum { value = GL_DOUBLE }; };
501 | template <> struct TypeToOpenGL { enum { value = GL_INT }; };
502 | template <> struct TypeToOpenGL { enum { value = GL_BYTE }; };
503 | template <> struct TypeToOpenGL { enum { value = GL_SHORT }; };
504 | template <> struct TypeToOpenGL { enum { value = GL_UNSIGNED_INT }; };
505 | template <> struct TypeToOpenGL { enum { value = GL_UNSIGNED_BYTE }; };
506 | template <> struct TypeToOpenGL { enum { value = GL_UNSIGNED_SHORT }; };
507 |
508 | // Groups a Buffer for vertices with a set of attributes for drawing. Can
509 | // optionally include a Buffer for indices too.
510 | //
511 | // Usage:
512 | //
513 | // // Initialization
514 | // VAO vao;
515 | // vao.create(shader, vertices, indices).attribute("vertices", 3).check();
516 | //
517 | // // Rendering
518 | // vao.draw(GL_TRIANGLE_STRIP);
519 | //
520 | struct VAO {
521 | // A holder for a Buffer so we can query information (i.e. number of vertices
522 | // for drawing). Type erasure is used so the VAO class doesn't need to be
523 | // templated.
524 | struct BufferHolder {
525 | virtual int currentTarget() const = 0;
526 | virtual unsigned int size() const = 0;
527 | };
528 | template
529 | struct BufferHolderImpl : BufferHolder {
530 | const Buffer &buffer;
531 | BufferHolderImpl(const Buffer &buffer) : buffer(buffer) {}
532 | int currentTarget() const { return buffer.currentTarget; }
533 | unsigned int size() const { return buffer.size(); }
534 | };
535 |
536 | // You should not need to access these
537 | unsigned int id;
538 | int stride, offset, indexType;
539 | const Shader *shader;
540 | const BufferHolder *vertices;
541 | const BufferHolder *indices;
542 |
543 | VAO() : id(), stride(), offset(), indexType(), shader(), vertices(), indices() {}
544 | ~VAO() { glDeleteVertexArrays(1, &id); delete vertices; delete indices; }
545 |
546 | // You should not need to bind a VAO directly
547 | void bind() const { glBindVertexArray(id); }
548 | void unbind() const { glBindVertexArray(0); }
549 |
550 | // Create a vertex array object referencing a shader and a vertex buffer.
551 | // The shader is used to query the location of attributes in attribute()
552 | // and the vertex buffer is used to determine the number of elements to
553 | // draw in draw() and drawInstanced().
554 | template
555 | VAO &create(const Shader &shader, const Buffer &vbo) {
556 | delete vertices;
557 | delete indices;
558 |
559 | this->shader = &shader;
560 | vertices = new BufferHolderImpl(vbo);
561 | indices = NULL;
562 | stride = sizeof(Vertex);
563 | indexType = GL_INVALID_ENUM;
564 |
565 | if (!id) glGenVertexArrays(1, &id);
566 | bind();
567 | vbo.bind();
568 | unbind();
569 |
570 | return *this;
571 | }
572 |
573 | // Create a vertex array object referencing a shader, a vertex buffer, and
574 | // an index buffer. The shader is used to query the location of attributes
575 | // in attribute() and the index buffer is used to determine the number of
576 | // elements to draw in draw() and drawInstanced().
577 | template
578 | VAO &create(const Shader &shader, const Buffer &vbo, const Buffer &ibo) {
579 | delete vertices;
580 | delete indices;
581 |
582 | this->shader = &shader;
583 | vertices = new BufferHolderImpl(vbo);
584 | indices = new BufferHolderImpl(ibo);
585 | stride = sizeof(Vertex);
586 | indexType = TypeToOpenGL::value;
587 |
588 | if (!id) glGenVertexArrays(1, &id);
589 | bind();
590 | vbo.bind();
591 | ibo.bind();
592 | unbind();
593 |
594 | return *this;
595 | }
596 |
597 | // Define an attribute called name in the provided shader. This attribute
598 | // has count elements of type T. If normalized is true, integer types are
599 | // mapped from 0 to 2^n-1 (where n is the bit count) to values from 0 to 1.
600 | //
601 | // Attributes should be declared in the order they are declared in the
602 | // vertex struct (assuming interleaved data). Call check() after declaring
603 | // all attributes to make sure the vertex struct is packed.
604 | template
605 | VAO &attribute(const char *name, int count, bool normalized = false) {
606 | int location = shader->attribute(name);
607 | bind();
608 | glEnableVertexAttribArray(location);
609 | glVertexAttribPointer(location, count, TypeToOpenGL::value, normalized, stride, (char *)NULL + offset);
610 | unbind();
611 | offset += count * sizeof(T);
612 | return *this;
613 | }
614 |
615 | // Validate VBO modes and attribute byte sizes
616 | void check();
617 |
618 | // Draw the attached VBOs
619 | void draw(int mode = GL_TRIANGLES) const {
620 | bind();
621 | if (indices) glDrawElements(mode, indices->size(), indexType, NULL);
622 | else glDrawArrays(mode, 0, vertices->size());
623 | unbind();
624 | }
625 |
626 | // Draw the attached VBOs using instancing
627 | void drawInstanced(int instances, int mode = GL_TRIANGLES) const {
628 | bind();
629 | if (indices) glDrawElementsInstanced(mode, indices->size(), indexType, NULL, instances);
630 | else glDrawArraysInstanced(mode, 0, vertices->size(), instances);
631 | unbind();
632 | }
633 | };
634 |
635 | #endif // GL4_H
636 |
--------------------------------------------------------------------------------
/proj1_life3d/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | g++ -I.. main.cpp ../gl4.cpp -lglut
3 |
--------------------------------------------------------------------------------
/proj1_life3d/README.md:
--------------------------------------------------------------------------------
1 | ## Project 1: Conway's Game of Life in 3D
2 |
3 | Controls:
4 |
5 | * Drag mouse: rotate camera
6 | * Spacebar: randomize cells
7 | * C: toggle first/third person camera
8 | * WASD: move camera in first person
9 |
10 | ## Introduction
11 |
12 | Conway's Game of Life is a cellular automaton developed by John Conway. The simulation takes place on a grid of cells that are in two possible states, alive or dead. Each step, the grid is evolved using simple rules that determine the state of the cell at the next step:
13 |
14 | * Any dead cell becomes alive if it has 3 live neighbors.
15 | * Any live cell becomes dead if it has 1 or 4 live neighbors.
16 |
17 | The neighbor count above checks all 8 neighbors. This produces interesting patterns that may replicate or travel and has been studied extensively.
18 |
19 | ## Extending to 3D
20 |
21 | The algorithm can be trivially extended to 3D, except the rules must be changed to maintain insteresting behavior. Each cell has 26 neighbors in 3D instead of 8 in 2D. I experimented with many rules but there were only a few that produced results worth noting.
22 |
23 | The first rule set resurrects dead cells if they have exactly 5 neighbors and kills live cells if they have less than 5 or exactly 8 neighbors. It was most interesting when the grid was initialized with each cell having a 5% chance of being alive. Unfortunately the directionless nature of the rules caused each pattern to grow outward in all directions which made for boring visuals.
24 |
25 | The second rule set resurrects dead cells if they have from 14 to 19 neighbors and kills live cells if they have less than 13 neighbors. When the grid is initialized with each cell having a 50% chance of being alive, the population shrinks over time and converges to stable 3D structures.
26 |
27 | ## Implementation
28 |
29 | My implementation uses OpenGL 4 and stores the 96x96x96 grid of cells in a 3D texture. I actually need two textures so I can read from one and write to the other (ping-pong rendering). Each step involves rendering to every 2D slice of the 3D write texture with a shader that counts the live neighbors in the 3D read texture and applies the rules to decide the next cell state. The grid domain is automatically wrapped by using the GL_REPEAT texture wrap mode along all 3 axes.
30 |
31 | The grid is visualized using instanced cubes with one instance per grid cell. If a cell is empty the vertex shader kills the instance by moving all vertices to (-2, -2, -2). A grid size of 96x96x96 was a good tradeoff between simulation detail and rendering speed.
32 |
33 | ## Ambient occlusion
34 |
35 | Ambient occlusion is a darkening effect that fakes indirect illumination (light rays bouncing off multiple surfaces before being seen by the viewer). Since our data is essentially voxels, ambient occlusion is actually easy to calculate. And since we already need to count all live neighbors for each cell we can actually get ambient occlusion for free!
36 |
37 | Short-range ambient occlusion, or direct corner darkening, can be added with one sample of the 3D texture using trilinear filtering. This value will be 1/8 for completely exposed corners and 7/8 for completely enclosed corners (since trilinear filtering interpolates between 8 texture lookups). This means 1 - trilinear_sample will cause corners to become darker. I ended up using clamp(1.5 - trilinear_sample, 0.0, 1.0) as the final short-range ambient occlusion factor.
38 |
39 | Long-range ambient occlusion darkens corners at larger scales. This will darken the ground under overhangs and the surfaces inside caves. One way to compute this is to blur the texture (in 3D) and use 1 - blurred_value to darken large-scale corners. However, instead of doing an expensive 3D blur every frame, we can instead compute the blur as a repeated series of small blurs over time. This way we get the blur for free since we are essentially doing a small blur by counting the neighbors. I added another channel to my 3D texture (now using the GL_RG format) to store this blurred value. The blurred value is updated using texture.g = mix(average.g, average.r, 0.01) and the final long-range ambient occlusion factor I used was clamp(1.5 - 3.0 * texture.g, 0.0, 1.0).
40 |
--------------------------------------------------------------------------------
/proj1_life3d/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "gl4.h"
3 |
4 | float width = 0, height = 0;
5 |
6 | bool keyUp = false;
7 | bool keyDown = false;
8 | bool keyLeft = false;
9 | bool keyRight = false;
10 | bool firstPerson = false;
11 | float cameraTransition = 0;
12 | float angleX = 0, angleY = 0;
13 | vec3 eye = vec3(0.5);
14 |
15 | Shader updateShader, displayShader;
16 | Texture textureA, textureB;
17 | FBO fbo(false);
18 |
19 | Buffer quadVertices;
20 | VAO quadLayout;
21 |
22 | Buffer cubeVertices;
23 | Buffer cubeIndices;
24 | VAO cubeLayout;
25 |
26 | void randomizeTextures() {
27 | const int size = 96;
28 | char data[size * size * size * 2];
29 | for (size_t i = 0; i < sizeof(data); i++) data[i] = (i & 1) ? 0x1F : 0xFF * ((rand() & 0xFF) < 127);
30 | textureA.create(size, size, size, GL_RG, GL_RG, GL_UNSIGNED_BYTE, GL_LINEAR, GL_REPEAT, data);
31 | textureB.create(size, size, size, GL_RG, GL_RG, GL_UNSIGNED_BYTE, GL_LINEAR, GL_REPEAT);
32 | }
33 |
34 | void setup() {
35 | randomizeTextures();
36 |
37 | glEnable(GL_CULL_FACE);
38 | glEnable(GL_DEPTH_TEST);
39 |
40 | // Update shader
41 | updateShader.vertexShader(glsl(
42 | in vec2 vertex;
43 | out vec2 coord;
44 | void main() {
45 | coord = vertex;
46 | gl_Position = vec4(vertex * 2.0 - 1.0, 0.0, 1.0);
47 | }
48 | )).fragmentShader(glsl(
49 | uniform sampler3D data;
50 | uniform float startZ;
51 | uniform float depth;
52 | in vec2 coord;
53 | out vec4 colors[8];
54 | void main() {
55 | // For each of the 8 slices we are doing this pass
56 | for (int i = 0; i < 8; i++) {
57 | // Find the 3D texture coordinate
58 | vec3 pos = vec3(coord, (startZ + i + 0.5) / depth);
59 | vec3 delta = vec3(1.0 / depth);
60 |
61 | // Count active neighbors
62 | vec2 neighbors = vec2(0.0);
63 | for (int z = -1; z <= 1; z++)
64 | for (int y = -1; y <= 1; y++)
65 | for (int x = -1; x <= 1; x++)
66 | neighbors += texture(data, pos + delta * vec3(x, y, z)).rg;
67 | vec2 self = texture(data, pos).rg;
68 | neighbors.r -= self.r;
69 |
70 | // Nice repeating structures (seed is randByte() < 13)
71 | // float next = mix(float(neighbors.r == 5.0), float(neighbors.r >= 5.0 && neighbors.r <= 7.0), self.r);
72 |
73 | // Generates 3D cloud structure (seed is randByte() < 127)
74 | float next = mix(float(neighbors.r >= 14.0 && neighbors.r <= 19.0), float(neighbors.r >= 13.0), self.r);
75 |
76 | // Calculate ambient occlusion by blurring the 3D buffer value using averaging over time
77 | colors[i] = vec4(next, mix(neighbors.r, neighbors.g, 0.99) / 27.0, 0.0, 0.0);
78 | }
79 | }
80 | )).link();
81 |
82 | // Display shader
83 | displayShader.vertexShader(glsl(
84 | uniform sampler3D data;
85 | uniform mat4 matrix;
86 | in vec3 vertex;
87 | out vec3 position;
88 | out vec3 coord;
89 | void main() {
90 | // Index into the 3D texture
91 | const int size = 96;
92 | vec3 offset = vec3(gl_InstanceID % size, (gl_InstanceID / size) % size, gl_InstanceID / (size * size));
93 | float value = texture(data, (offset + 0.5) / size).r;
94 |
95 | // If the cell is empty, move it out of the view so it won't be drawn
96 | if (value > 0.5) {
97 | gl_Position = matrix * vec4((vertex + offset) / size, 1.0);
98 | position = vertex;
99 | } else {
100 | gl_Position = vec4(-2.0, -2.0, -2.0, 1.0);
101 | }
102 |
103 | // 3D Texture coordinate
104 | coord = (offset + vertex) / size;
105 | }
106 | )).fragmentShader(glsl(
107 | uniform sampler3D data;
108 | in vec3 position;
109 | in vec3 coord;
110 | out vec4 color;
111 | void main() {
112 | // Calculate surface normal
113 | vec3 normal = normalize(cross(dFdx(position), dFdy(position)));
114 |
115 | // Calculate ambient occlusion
116 | vec2 value = texture(data, coord).rg;
117 | float smallScale = clamp(1.5 - value.r, 0.0, 1.0);
118 | float largeScale = clamp(1.5 - 3.0 * value.g, 0.0, 1.0);
119 | color = vec4(smallScale * largeScale);
120 |
121 | // No ambient occlusion on boundary fragments
122 | vec3 boundary = coord + normal * 0.001;
123 | if (boundary != clamp(boundary, 0.0, 1.0)) color = vec4(1.0);
124 |
125 | // Add some color
126 | vec3 light = normalize(vec3(1.0, 3.0, 2.0));
127 | color *= mix(vec4(0.4, 0.5, 0.6, 0.0), vec4(0.6, 0.65, 0.7, 0.0), 0.5 + 0.5 * dot(light, normal));
128 | }
129 | )).link();
130 |
131 | // Vertices
132 | for (int i = 0; i < 8; i++) cubeVertices << vec3(!!(i & 1), !!(i & 2), !!(i & 4));
133 | for (int i = 0; i < 4; i++) quadVertices << vec2(!!(i & 1), !!(i & 2));
134 | cubeVertices.upload();
135 | quadVertices.upload();
136 |
137 | // Indices
138 | cubeIndices << 0 << 2 << 3 << 1;
139 | cubeIndices << 4 << 5 << 7 << 6;
140 | cubeIndices << 0 << 1 << 5 << 4;
141 | cubeIndices << 2 << 6 << 7 << 3;
142 | cubeIndices << 0 << 4 << 6 << 2;
143 | cubeIndices << 1 << 3 << 7 << 5;
144 | cubeIndices.upload(GL_ELEMENT_ARRAY_BUFFER);
145 |
146 | // Vertex array layout
147 | cubeLayout.create(displayShader, cubeVertices, cubeIndices).attribute("vertex", 3).check();
148 | quadLayout.create(updateShader, quadVertices).attribute("vertex", 2).check();
149 | }
150 |
151 | void draw() {
152 | // Set up the camera
153 | mat4 matrix;
154 | matrix.perspective(45, width / height, 0.001, 10).translate(0, 0, -2 * (1 - cameraTransition));
155 | matrix.rotateX(angleX).rotateY(angleY).translate(-eye * cameraTransition - vec3(0.5 * (1 - cameraTransition)));
156 |
157 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
158 |
159 | // Render the volume using instanced cubes
160 | displayShader.use();
161 | displayShader.uniform("matrix", matrix);
162 | textureA.bind();
163 | cubeLayout.drawInstanced(textureA.width * textureA.height * textureA.depth, GL_QUADS);
164 | textureA.unbind();
165 | displayShader.unuse();
166 |
167 | glutSwapBuffers();
168 | }
169 |
170 | void update() {
171 | const int step = 8;
172 |
173 | // Update a single step
174 | updateShader.use();
175 | updateShader.uniformFloat("depth", textureA.depth);
176 | textureA.bind();
177 | for (int z = 0; z < textureA.depth; z += step) {
178 | for (int attachment = 0; attachment < step; attachment++) {
179 | fbo.attachColor(textureB, attachment, z + attachment);
180 | }
181 | fbo.check();
182 |
183 | updateShader.uniformFloat("startZ", z);
184 | fbo.bind();
185 | quadLayout.draw(GL_TRIANGLE_STRIP);
186 | fbo.unbind();
187 | }
188 | textureA.unbind();
189 | updateShader.unuse();
190 | textureA.swapWith(textureB);
191 |
192 | // Transition from first person to third person and back
193 | cameraTransition = firstPerson * 0.1 + cameraTransition * 0.9;
194 | if (!firstPerson) eye = 0.05 + eye * 0.9;
195 |
196 | // Move around in first person
197 | if (firstPerson) {
198 | const float speed = 0.005;
199 | const float deg2rad = M_PI / 180;
200 | float sx = sin(angleX * deg2rad), cx = cos(angleX * deg2rad);
201 | float sy = sin(angleY * deg2rad), cy = cos(angleY * deg2rad);
202 | eye += vec3(cy, 0, -sy) * (keyRight - keyLeft) * speed;
203 | eye += vec3(sy * cx, -sx, cy * cx) * (keyDown - keyUp) * speed;
204 | }
205 |
206 | draw();
207 | }
208 |
209 | // For calculating mouse deltas
210 | int oldX, oldY;
211 |
212 | void mousemove(int x, int y) {
213 | const float speed = firstPerson ? 0.5 : 1;
214 | angleY -= (x - oldX) * speed;
215 | angleX -= (y - oldY) * speed;
216 | oldX = x;
217 | oldY = y;
218 | angleX = fmaxf(-90, fminf(90, angleX));
219 | }
220 |
221 | void mousedown(int, int, int x, int y) {
222 | oldX = x;
223 | oldY = y;
224 | }
225 |
226 | void keydown(unsigned char key, int, int) {
227 | switch (key) {
228 | case 27: exit(0); break;
229 | case ' ': randomizeTextures(); break;
230 | case 'c': firstPerson = !firstPerson; break;
231 | case 'w': keyUp = true; break;
232 | case 'a': keyLeft = true; break;
233 | case 's': keyDown = true; break;
234 | case 'd': keyRight = true; break;
235 | }
236 | }
237 |
238 | void keyup(unsigned char key, int, int) {
239 | switch (key) {
240 | case 'w': keyUp = false; break;
241 | case 'a': keyLeft = false; break;
242 | case 's': keyDown = false; break;
243 | case 'd': keyRight = false; break;
244 | }
245 | }
246 |
247 | void resize(int w, int h) {
248 | width = w;
249 | height = h;
250 | glViewport(0, 0, w, h);
251 | }
252 |
253 | int main(int argc, char *argv[]) {
254 | enum { WIDTH = 800, HEIGHT = 600 };
255 | glutInit(&argc, argv);
256 | glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH | GLUT_MULTISAMPLE);
257 | glutCreateWindow("cs195v - life");
258 | glutReshapeWindow(WIDTH, HEIGHT);
259 | glutDisplayFunc(draw);
260 | glutKeyboardFunc(keydown);
261 | glutKeyboardUpFunc(keyup);
262 | glutReshapeFunc(resize);
263 | glutIdleFunc(update);
264 | glutMotionFunc(mousemove);
265 | glutMouseFunc(mousedown);
266 | setup();
267 | resize(WIDTH, HEIGHT);
268 | glutMainLoop();
269 | return 0;
270 | }
271 |
--------------------------------------------------------------------------------
/proj2_tess/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | g++ -I.. main.cpp ../gl4.cpp -lglut
3 |
--------------------------------------------------------------------------------
/proj2_tess/README.md:
--------------------------------------------------------------------------------
1 | ## Project 2: Noise and Tessellation
2 |
3 | Controls:
4 |
5 | * Drag mouse: rotate camera
6 | * Tab: toggle wireframe
7 | * WASD: move camera
8 | * = or -: Increase or decrease the tessellation level
9 |
10 | ## Noise
11 |
12 | My terrain uses riged multifractal noise on top of the [2D simplex noise](https://github.com/ashima/webgl-noise/blob/master/src/noise2D.glsl) implementation by Ian McEwan. Seven octaves are used where every step scales the coordinate by 2 and the weight by 0.5. The creases in the noise are caused by wrapping a call to absolute value around the noise lookup.
13 |
14 | ## Tessellation
15 |
16 | A base 128x128 grid of quads is tessellated every frame using a tesselation shader. The tesselation level is proportional to 1 / (1 + distance_from_eye). Fractional spacing is used to smoothly blend between detail levels. Cracks were avoided by calculating the tessellation level for an edge at its midpoint so neighboring quads will compute the same value.
17 |
18 | ## Shading
19 |
20 | Crease darkening (pseudo ambient-occlusion) was computed from the terrain noise function. Each crease is the result of an absolute value at a certain octave level. The minimum of all unweighted octave levels will be a value that is zero on creases and positive in between creases. Multiplying this value by the color performs crease darkening. This needs to be done in the fragment shader to prevent artifacts due to vertex interpolation, so both the tess evaluation shader and the fragment shader have copies of the terrain noise function.
21 |
22 | Normals are calculated cheaply by reusing the terrain height calculation used in crease darkening. The normal for a pixel is calculated using normalize(cross(dFdx(position), dFdy(position))) with the position obtained from the terrain function. This is an approximate normal calculated using screen-space partial derivatives, which are implemented with a finite difference from two neighboring shader units (one in x and one in y). Using this method causes slight artifacts but is much better than interpolating per-vertex normals.
23 |
24 | ## Post-processing
25 |
26 | Variable-density fog was added as a post-process. The first pass writes to two render targets, one for color and one for position. The second pass renders a fullscreen quad and computes fog as the integral of e^-y over the line of sight from the eye to the position.
27 |
--------------------------------------------------------------------------------
/proj2_tess/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "gl4.h"
3 |
4 | float width = 800, height = 600;
5 | float angleX = 0, angleY = 0;
6 | vec3 eye;
7 |
8 | Shader terrainShader, fogShader;
9 | float maxTessLevel = 64;
10 |
11 | Buffer gridVertices;
12 | VAO gridLayout;
13 |
14 | Buffer quadVertices;
15 | VAO quadLayout;
16 |
17 | FBO fbo;
18 | Texture colorTexture;
19 | Texture positionTexture;
20 |
21 | void setup() {
22 | terrainShader.vertexShader(glsl(
23 | uniform vec3 eye;
24 | in vec3 vertex;
25 | out vec3 vPosition;
26 | out float distance;
27 | void main() {
28 | vPosition = vertex;
29 | distance = length(vPosition - eye);
30 | }
31 | )).tessControlShader(glsl(
32 | uniform mat4 matrix;
33 | uniform vec3 eye;
34 | uniform float maxTessLevel;
35 | layout(vertices=4) out;
36 | in vec3 vPosition[];
37 | in float distance[];
38 | out vec3 tcPosition[];
39 | float tessLevel(float distance) {
40 | return maxTessLevel / (1.0 + distance);
41 | }
42 | void main() {
43 | // Wrap patch to always be centered around the eye
44 | vec3 delta = (vPosition[0] - eye) * 0.03125;
45 | delta.xz -= floor(delta.xz + 0.5);
46 | delta = delta / 0.03125 + eye - vPosition[0];
47 |
48 | tcPosition[gl_InvocationID] = vPosition[gl_InvocationID] + delta;
49 | if (gl_InvocationID == 0) {
50 | vec3 v0 = vPosition[0] + delta;
51 | vec3 v1 = vPosition[1] + delta;
52 | vec3 v2 = vPosition[2] + delta;
53 | vec3 v3 = vPosition[3] + delta;
54 |
55 | // When the eye is looking up, use the top AABB points
56 | if (matrix[1][2] > 0.0) {
57 | v0.y++;
58 | v1.y++;
59 | v2.y++;
60 | v3.y++;
61 | }
62 |
63 | vec4 t0 = matrix * vec4(v0, 1.0);
64 | vec4 t1 = matrix * vec4(v1, 1.0);
65 | vec4 t2 = matrix * vec4(v2, 1.0);
66 | vec4 t3 = matrix * vec4(v3, 1.0);
67 | t0.x /= t0.w;
68 | t1.x /= t1.w;
69 | t2.x /= t2.w;
70 | t3.x /= t3.w;
71 | if (
72 | // Frustum culling
73 | (t0.x < -1.0 && t1.x < -1.0 && t2.x < -1.0 && t3.x < -1.0) ||
74 | (t0.x > +1.0 && t1.x > +1.0 && t2.x > +1.0 && t3.x > +1.0) ||
75 | (t0.z < -1.0 && t1.z < -1.0 && t2.z < -1.0 && t3.z < -1.0)
76 | ) {
77 | gl_TessLevelInner[0] = 0.0;
78 | gl_TessLevelInner[1] = 0.0;
79 | gl_TessLevelOuter[0] = 0.0;
80 | gl_TessLevelOuter[1] = 0.0;
81 | gl_TessLevelOuter[2] = 0.0;
82 | gl_TessLevelOuter[3] = 0.0;
83 | } else {
84 | float d0 = length(v0 - eye);
85 | float d1 = length(v1 - eye);
86 | float d2 = length(v2 - eye);
87 | float d3 = length(v3 - eye);
88 | float tessLevelInner = tessLevel((d0 + d1 + d2 + d3) * 0.25);
89 | gl_TessLevelInner[0] = tessLevelInner;
90 | gl_TessLevelInner[1] = tessLevelInner;
91 | gl_TessLevelOuter[0] = tessLevel((d0 + d2) * 0.5);
92 | gl_TessLevelOuter[1] = tessLevel((d0 + d1) * 0.5);
93 | gl_TessLevelOuter[2] = tessLevel((d1 + d3) * 0.5);
94 | gl_TessLevelOuter[3] = tessLevel((d2 + d3) * 0.5);
95 | }
96 | }
97 | }
98 | )).tessEvalShader(glsl(
99 | layout(quads, fractional_even_spacing) in;
100 | in vec3 tcPosition[];
101 | out vec3 point;
102 | uniform mat4 matrix;
103 |
104 | //
105 | // Description : Array and textureless GLSL 2D simplex noise function.
106 | // Author : Ian McEwan, Ashima Arts.
107 | // Maintainer : ijm
108 | // Lastmod : 20110822 (ijm)
109 | // License : Copyright (C) 2011 Ashima Arts. All rights reserved.
110 | // Distributed under the MIT License. See LICENSE file.
111 | // https://github.com/ashima/webgl-noise
112 | //
113 |
114 | vec3 mod289(vec3 x) {
115 | return x - floor(x * (1.0 / 289.0)) * 289.0;
116 | }
117 |
118 | vec2 mod289(vec2 x) {
119 | return x - floor(x * (1.0 / 289.0)) * 289.0;
120 | }
121 |
122 | vec3 permute(vec3 x) {
123 | return mod289(((x*34.0)+1.0)*x);
124 | }
125 |
126 | float snoise(vec2 v)
127 | {
128 | const vec4 C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0
129 | 0.366025403784439, // 0.5*(sqrt(3.0)-1.0)
130 | -0.577350269189626, // -1.0 + 2.0 * C.x
131 | 0.024390243902439); // 1.0 / 41.0
132 | // First corner
133 | vec2 i = floor(v + dot(v, C.yy) );
134 | vec2 x0 = v - i + dot(i, C.xx);
135 |
136 | // Other corners
137 | vec2 i1;
138 | //i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0
139 | //i1.y = 1.0 - i1.x;
140 | i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
141 | // x0 = x0 - 0.0 + 0.0 * C.xx ;
142 | // x1 = x0 - i1 + 1.0 * C.xx ;
143 | // x2 = x0 - 1.0 + 2.0 * C.xx ;
144 | vec4 x12 = x0.xyxy + C.xxzz;
145 | x12.xy -= i1;
146 |
147 | // Permutations
148 | i = mod289(i); // Avoid truncation effects in permutation
149 | vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))
150 | + i.x + vec3(0.0, i1.x, 1.0 ));
151 |
152 | vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);
153 | m = m*m ;
154 | m = m*m ;
155 |
156 | // Gradients: 41 points uniformly over a line, mapped onto a diamond.
157 | // The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287)
158 |
159 | vec3 x = 2.0 * fract(p * C.www) - 1.0;
160 | vec3 h = abs(x) - 0.5;
161 | vec3 ox = floor(x + 0.5);
162 | vec3 a0 = x - ox;
163 |
164 | // Normalise gradients implicitly by scaling m
165 | // Approximation of: m *= inversesqrt( a0*a0 + h*h );
166 | m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
167 |
168 | // Compute final noise value at P
169 | vec3 g;
170 | g.x = a0.x * x0.x + h.x * x0.y;
171 | g.yz = a0.yz * x12.xz + h.yz * x12.yw;
172 | return 130.0 * dot(m, g);
173 | }
174 |
175 | // Return all octaves directly because they are used to calculate
176 | // the diffuse color in the fragment shader. Calculating diffuse
177 | // color in the tess eval shader leads to horrible interpolation
178 | // because the abs() value doesn't interpolate across the origin.
179 | float terrain(vec2 coord) {
180 | float height = 0.0;
181 | float weight = 0.5;
182 | vec2 offset = vec2(0.0);
183 | coord *= 0.25;
184 | for (int i = 0; i < 7; i++) {
185 | height += abs(snoise(coord + offset)) * weight;
186 | offset += vec2(7.434387, 1.4567845);
187 | coord *= 2.0;
188 | weight *= 0.5;
189 | }
190 | return height * height * height;
191 | }
192 |
193 | void main() {
194 | // Bilinear interpolation
195 | vec3 p01 = mix(tcPosition[0], tcPosition[1], gl_TessCoord.x);
196 | vec3 p23 = mix(tcPosition[2], tcPosition[3], gl_TessCoord.x);
197 | point = mix(p01, p23, gl_TessCoord.y);
198 |
199 | // Compute vertex height
200 | point.y = terrain(point.xz);
201 | }
202 | )).geometryShader(glsl(
203 | layout(triangles) in;
204 | layout(triangle_strip, max_vertices = 3) out;
205 | uniform mat4 matrix;
206 | in vec3 point[];
207 | out vec3 p;
208 | out vec3 baryCoord;
209 | void main() {
210 | baryCoord = vec3(1.0, 0.0, 0.0);
211 | p = point[0];
212 | gl_Position = matrix * vec4(point[0], 1);
213 | EmitVertex();
214 |
215 | baryCoord = vec3(0.0, 1.0, 0.0);
216 | p = point[1];
217 | gl_Position = matrix * vec4(point[1], 1);
218 | EmitVertex();
219 |
220 | baryCoord = vec3(0.0, 0.0, 1.0);
221 | p = point[2];
222 | gl_Position = matrix * vec4(point[2], 1);
223 | EmitVertex();
224 |
225 | EndPrimitive();
226 | }
227 | )).fragmentShader(glsl(
228 | // We need high precision for normal calculation via derivatives
229 | precision highp float;
230 |
231 | //
232 | // Description : Array and textureless GLSL 2D simplex noise function.
233 | // Author : Ian McEwan, Ashima Arts.
234 | // Maintainer : ijm
235 | // Lastmod : 20110822 (ijm)
236 | // License : Copyright (C) 2011 Ashima Arts. All rights reserved.
237 | // Distributed under the MIT License. See LICENSE file.
238 | // https://github.com/ashima/webgl-noise
239 | //
240 |
241 | vec3 mod289(vec3 x) {
242 | return x - floor(x * (1.0 / 289.0)) * 289.0;
243 | }
244 |
245 | vec2 mod289(vec2 x) {
246 | return x - floor(x * (1.0 / 289.0)) * 289.0;
247 | }
248 |
249 | vec3 permute(vec3 x) {
250 | return mod289(((x*34.0)+1.0)*x);
251 | }
252 |
253 | float snoise(vec2 v)
254 | {
255 | const vec4 C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0
256 | 0.366025403784439, // 0.5*(sqrt(3.0)-1.0)
257 | -0.577350269189626, // -1.0 + 2.0 * C.x
258 | 0.024390243902439); // 1.0 / 41.0
259 | // First corner
260 | vec2 i = floor(v + dot(v, C.yy) );
261 | vec2 x0 = v - i + dot(i, C.xx);
262 |
263 | // Other corners
264 | vec2 i1;
265 | //i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0
266 | //i1.y = 1.0 - i1.x;
267 | i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
268 | // x0 = x0 - 0.0 + 0.0 * C.xx ;
269 | // x1 = x0 - i1 + 1.0 * C.xx ;
270 | // x2 = x0 - 1.0 + 2.0 * C.xx ;
271 | vec4 x12 = x0.xyxy + C.xxzz;
272 | x12.xy -= i1;
273 |
274 | // Permutations
275 | i = mod289(i); // Avoid truncation effects in permutation
276 | vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 ))
277 | + i.x + vec3(0.0, i1.x, 1.0 ));
278 |
279 | vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0);
280 | m = m*m ;
281 | m = m*m ;
282 |
283 | // Gradients: 41 points uniformly over a line, mapped onto a diamond.
284 | // The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287)
285 |
286 | vec3 x = 2.0 * fract(p * C.www) - 1.0;
287 | vec3 h = abs(x) - 0.5;
288 | vec3 ox = floor(x + 0.5);
289 | vec3 a0 = x - ox;
290 |
291 | // Normalise gradients implicitly by scaling m
292 | // Approximation of: m *= inversesqrt( a0*a0 + h*h );
293 | m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h );
294 |
295 | // Compute final noise value at P
296 | vec3 g;
297 | g.x = a0.x * x0.x + h.x * x0.y;
298 | g.yz = a0.yz * x12.xz + h.yz * x12.yw;
299 | return 130.0 * dot(m, g);
300 | }
301 |
302 | // Return the terrain height and the "ambient occlusion" factor.
303 | vec2 terrain(vec2 coord) {
304 | float height = 0.0;
305 | float weight = 0.5;
306 | float minValue = 1.0;
307 | vec2 offset = vec2(0.0);
308 | coord *= 0.25;
309 | for (int i = 0; i < 7; i++) {
310 | float value = abs(snoise(coord + offset));
311 | minValue = min(value, minValue);
312 | height += value * weight;
313 | offset += vec2(7.434387, 1.4567845);
314 | coord *= 2.0;
315 | weight *= 0.5;
316 | }
317 | return vec2(height * height * height, minValue);
318 | }
319 |
320 | uniform float wireframe;
321 | in vec3 p;
322 | in vec3 baryCoord;
323 | out vec4 color;
324 | out vec3 position;
325 |
326 | void main() {
327 | // Calculate terrain height and ambient occlusion simultaneously
328 | vec2 tuple = terrain(p.xz);
329 | position = vec3(p.x, tuple.x, p.z);
330 |
331 | // Calculate the normal using the value of position in adjacent pixels
332 | vec3 normal = normalize(cross(dFdx(position), dFdy(position)));
333 |
334 | // Do color and lighting calculations
335 | const vec3 light = vec3(0.707106781, 0.707106781, 0.0);
336 | float weight = (max(0.0, dot(normal, light)) * 0.8 + 0.2) * (tuple.y * 0.75 + 0.25);
337 | float grassRock = clamp((position.y - normal.y * 0.25) * 50.0 + 5.0, 0.0, 1.0);
338 | color = vec4(mix(vec3(0.75 + min(1.0, position.y * 2.0), 1.0, 0.0), vec3(1.0), grassRock) * weight, 1.0);
339 |
340 | // Calculate an anti-aliased triangle wireframe using the screen-space derivatives of the barycentric coordinates
341 | vec3 g = smoothstep(vec3(0.0), fwidth(baryCoord * 0.875), abs(fract(baryCoord - 0.5) - 0.5));
342 | color.a = mix(1.0, 0.5 + 0.5 * min(min(g.x, g.y), g.z), wireframe);
343 | }
344 | )).link();
345 |
346 | fogShader.vertexShader(glsl(
347 | uniform mat4 invMatrix;
348 | in vec2 vertex;
349 | out vec3 ray;
350 | out vec2 coord;
351 | void main() {
352 | coord = vertex;
353 | gl_Position = vec4(vertex * 2.0 - 1.0, 0.0, 1.0);
354 |
355 | // Calculate the view ray
356 | vec4 far = invMatrix * vec4(gl_Position.xy, 1.0, 1.0);
357 | vec4 near = invMatrix * vec4(gl_Position.xy, 0.5, 1.0);
358 | ray = normalize(far.xyz / far.w - near.xyz / near.w);
359 | }
360 | )).fragmentShader(glsl(
361 | uniform vec3 eye;
362 | uniform sampler2D colorTexture;
363 | uniform sampler2D positionTexture;
364 | in vec2 coord;
365 | in vec3 ray;
366 | out vec4 color;
367 | void main() {
368 | color = texture(colorTexture, coord);
369 | vec3 position = texture(positionTexture, coord).xyz;
370 |
371 | const float scale = 10.0;
372 | float weight;
373 | if (position == vec3(0.0)) {
374 | // Infinite distance exponential height-based fog
375 | float slope = ray.y;
376 | if (slope > 0.0) {
377 | // Finite total density upwards
378 | float densityIntegral = exp(-eye.y * scale) / slope;
379 | weight = 1.0 - 1.0 / (1.0 + densityIntegral);
380 | } else {
381 | // Special-case this because the calculation is infinite
382 | weight = 1.0;
383 | }
384 | color = vec4(0.0, 0.0, 0.0, 1.0);
385 | } else {
386 | // Finite distance exponential height-based fog
387 | vec3 look = position - eye;
388 | float len = length(look);
389 | float slope = look.y / len;
390 | float densityIntegral = exp(-position.y * scale) * (exp(scale * len * slope) - 1) / slope;
391 | weight = 1.0 - 1.0 / (1.0 + densityIntegral);
392 | }
393 |
394 | // Apply wireframe after fog
395 | color = vec4(mix(color.rgb, vec3(0.8, 0.85, 1.0), weight) * color.a, 1.0);
396 | }
397 | )).link();
398 |
399 | fogShader.use();
400 | fogShader.uniformInt("colorTexture", 0);
401 | fogShader.uniformInt("positionTexture", 1);
402 | fogShader.unuse();
403 |
404 | const int size = 128;
405 | const float scale = 0.125;
406 | for (int z = -size; z < size; z++) {
407 | for (int x = -size; x < size; x++) {
408 | gridVertices << vec3(x, 0, z) * scale;
409 | gridVertices << vec3(x, 0, z + 1) * scale;
410 | gridVertices << vec3(x + 1, 0, z) * scale;
411 | gridVertices << vec3(x + 1, 0, z + 1) * scale;
412 | }
413 | }
414 | gridVertices.upload();
415 | gridLayout.create(terrainShader, gridVertices).attribute("vertex", 3).check();
416 |
417 | // Vertices
418 | quadVertices << vec2(0, 0) << vec2(1, 0) << vec2(0, 1) << vec2(1, 1);
419 | quadVertices.upload();
420 | quadLayout.create(fogShader, quadVertices).attribute("vertex", 2).check();
421 | }
422 |
423 | bool left = false;
424 | bool right = false;
425 | bool forward = false;
426 | bool backward = false;
427 | bool wireframe = false;
428 |
429 | void draw() {
430 | // Set up the camera
431 | mat4 matrix;
432 | mat4 modelview;
433 | matrix.perspective(45, width / height, 0.001, 100);
434 | modelview.rotateX(angleX).rotateY(angleY).translate(-eye);
435 | matrix *= modelview;
436 |
437 | fbo.bind();
438 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
439 | glEnable(GL_DEPTH_TEST);
440 | terrainShader.use();
441 | terrainShader.uniformFloat("maxTessLevel", maxTessLevel);
442 | terrainShader.uniformFloat("wireframe", wireframe);
443 | terrainShader.uniform("matrix", matrix);
444 | terrainShader.uniform("eye", eye);
445 | glPatchParameteri(GL_PATCH_VERTICES, 4);
446 | gridLayout.draw(GL_PATCHES);
447 | terrainShader.unuse();
448 | glDisable(GL_DEPTH_TEST);
449 | fbo.unbind();
450 |
451 | fogShader.use();
452 | fogShader.uniform("invMatrix", matrix.invert());
453 | fogShader.uniform("eye", eye);
454 | colorTexture.bind(0);
455 | positionTexture.bind(1);
456 | quadLayout.draw(GL_TRIANGLE_STRIP);
457 | positionTexture.unbind(1);
458 | colorTexture.unbind(0);
459 | fogShader.unuse();
460 |
461 | glutSwapBuffers();
462 | }
463 |
464 | // For calculating mouse deltas
465 | int oldX, oldY;
466 |
467 | void mousemove(int x, int y) {
468 | angleY -= x - oldX;
469 | angleX -= y - oldY;
470 | oldX = x;
471 | oldY = y;
472 | angleX = fmaxf(-90, fminf(90, angleX));
473 | }
474 |
475 | void mousedown(int, int, int x, int y) {
476 | oldX = x;
477 | oldY = y;
478 | }
479 |
480 | void keydown(unsigned char key, int, int) {
481 | if (key == 27) exit(0);
482 | if (key == '\t') wireframe = !wireframe;
483 | if (key == '=') maxTessLevel *= 1.1;
484 | if (key == '-') maxTessLevel /= 1.1;
485 | if (maxTessLevel < 2) maxTessLevel = 2;
486 | if (maxTessLevel > 256) maxTessLevel = 256;
487 |
488 | if (key == 'a') left = true;
489 | if (key == 'd') right = true;
490 | if (key == 'w') forward = true;
491 | if (key == 's') backward = true;
492 | }
493 |
494 | void keyup(unsigned char key, int, int) {
495 | if (key == 'a') left = false;
496 | if (key == 'd') right = false;
497 | if (key == 'w') forward = false;
498 | if (key == 's') backward = false;
499 | }
500 |
501 | #include
502 | #include
503 |
504 | float Seconds() {
505 | // jump through the hoops to get the current number of microseconds since the last second tick (0 <= microseconds < 1000000)
506 | timeval tv;
507 | gettimeofday(&tv, NULL);
508 | unsigned int microseconds = tv.tv_usec;
509 |
510 | // store the old_microseconds in a static variable, but set it to microseconds the first time so the first call to Seconds() returns 0
511 | static unsigned int old_microseconds = 0xFFFFFFFF;
512 | if (old_microseconds == 0xFFFFFFFF) old_microseconds = microseconds;
513 |
514 | // handle overflow so we don't generate a huge seconds value when microseconds wraps back to 0
515 | if (old_microseconds > microseconds) old_microseconds -= 1000000;
516 |
517 | float seconds = (float)(microseconds - old_microseconds) / 1000000.0f;
518 | old_microseconds = microseconds;
519 | return seconds;
520 | }
521 |
522 | void update() {
523 | const float seconds = Seconds();
524 | float ax = angleX * (M_PI / 180);
525 | float ay = angleY * (M_PI / 180);
526 | vec3 lr = vec3(cos(ay), 0, -sin(ay));
527 | vec3 fb = vec3(-sin(ay) * cos(ax), sin(ax), -cos(ay) * cos(ax));
528 | eye += (fb * (forward - backward) + lr * (right - left)) * seconds;
529 | draw();
530 | }
531 |
532 | void resize(int w, int h) {
533 | width = w;
534 | height = h;
535 | glViewport(0, 0, w, h);
536 |
537 | colorTexture.create(w, h, 1, GL_RGBA, GL_RGBA, GL_UNSIGNED_BYTE, GL_NEAREST, GL_CLAMP_TO_EDGE);
538 | positionTexture.create(w, h, 1, GL_RGB32F, GL_RGB, GL_FLOAT, GL_NEAREST, GL_CLAMP_TO_EDGE);
539 | fbo.attachColor(colorTexture, 0).attachColor(positionTexture, 1).check();
540 | }
541 |
542 | int main(int argc, char *argv[]) {
543 | glutInit(&argc, argv);
544 | glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
545 | glutCreateWindow("Example");
546 | glutReshapeWindow(width, height);
547 | glutDisplayFunc(draw);
548 | glutKeyboardFunc(keydown);
549 | glutKeyboardUpFunc(keyup);
550 | glutMotionFunc(mousemove);
551 | glutMouseFunc(mousedown);
552 | glutReshapeFunc(resize);
553 | glutIdleFunc(update);
554 | setup();
555 | glutMainLoop();
556 | return 0;
557 | }
558 |
--------------------------------------------------------------------------------
/proj3_nbody/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | g++ -I.. main.cpp ../gl4.cpp -lglut
3 |
--------------------------------------------------------------------------------
/proj3_nbody/README.md:
--------------------------------------------------------------------------------
1 | ## Project 3: N-Body Simulation
2 |
3 | Controls:
4 |
5 | * Drag mouse: rotate camera
6 | * Scroll wheel: zoom camera
7 | * R: reset particles
8 | * P: pause simulation
9 | * O: change post-processing effect
10 |
11 | ## Introduction
12 |
13 | This project implements a simulation of a large number of point masses acting under gravity. It uses the simple O(n2) algorithm that computes the force on each point mass due to all other point masses. However, the GPU can still simulate over 16,000 particles interacting at 60 fps.
14 |
15 | ## Implementation
16 |
17 | This was implemented using OpenGL 4 with Verlet integration. Under Verlet integration, the next position of the particle is calculated using only the previous two positions and the acceleration: next = 2 * current - previous + acceleration. I stored this information for all 16,384 particles in three 128x128 textures (for the previous, current, and next positions).
18 |
19 | The particles were rendered using point primitives with a size inversely proportional to the distance from the camera. Anti-aliasing was computed using the distance from the center of the point to gl_FragCoord.
20 |
21 | The initial configuration used was two spherical wire cages generated from two long strings of particles. Particle positions were generated by rotating the vector (0, 0, 1) by an increasing angle about six different axes and then displacing the result either left or right. This generated more interesting motion than a uniformly random initial state because the intersections of wires quickly created local clumps of particles.
22 |
23 | ## Post Processing
24 |
25 | I implemented two post-processing shaders: accumulation trails and hexagonal bokeh. The details of the hexagonal bokeh implementation can be found in the Siggraph 2011 talk [More Performance! Five Rendering Ideas from Battlefield 3 and Need for Speed: The Run](http://advances.realtimerendering.com/s2011/White,%20BarreBrisebois-%20Rendering%20in%20BF3%20%28Siggraph%202011%20Advances%20in%20Real-Time%20Rendering%20Course%29.pdf).
26 |
--------------------------------------------------------------------------------
/proj3_nbody/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "gl4.h"
3 |
4 | enum PostProcess {
5 | None,
6 | Accumulation,
7 | Bokeh,
8 | PostProcessCount
9 | };
10 |
11 | const int bufferWidth = 128;
12 | const int bufferHeight = 128;
13 |
14 | bool paused = false;
15 | PostProcess postProcess = None;
16 | float width = 800, height = 600;
17 | float angleX = 0, angleY = 0, zoomZ = 10;
18 |
19 | Buffer point;
20 | Buffer quad;
21 | VAO pointLayout;
22 | VAO quadLayout;
23 |
24 | Shader updateShader;
25 | Shader drawShader;
26 | FBO fbo;
27 |
28 | Texture prevPositions;
29 | Texture currPositions;
30 | Texture nextPositions;
31 |
32 | Texture renderTarget;
33 | Texture accumulationTexture;
34 | Texture bokehScratchA;
35 | Texture bokehScratchB;
36 |
37 | Shader accumulationShader;
38 | Shader bokehFirstPass;
39 | Shader bokehSecondPass;
40 |
41 | inline float frand() {
42 | return (float)rand() / (float)RAND_MAX;
43 | }
44 |
45 | void reset() {
46 | std::vector points;
47 | float offset = frand() * 100;
48 | for (int i = 0; i < bufferWidth * bufferHeight; i++) {
49 | float t = 20 * (offset + (float)i / (float)(bufferWidth * bufferHeight));
50 | if (i & 1) t += 100;
51 | mat4 matrix;
52 | matrix.rotateX(t * 93).rotateY(t * 37).rotateZ(t * 17);
53 | matrix.rotateX(t * 5).rotateY(t * 147).rotateZ(t * 71);
54 | vec4 vertex = matrix * vec4(0, 0, 1, 0);
55 | vertex.x = i & 1 ? vertex.x + 1 : -1 - vertex.x;
56 | points.push_back(vec3(vertex.x, vertex.y, vertex.z) * 4);
57 | }
58 |
59 | prevPositions.create(bufferWidth, bufferHeight, 1, GL_RGB32F, GL_RGB, GL_FLOAT, GL_NEAREST, GL_CLAMP_TO_EDGE, points.data());
60 | currPositions.create(bufferWidth, bufferHeight, 1, GL_RGB32F, GL_RGB, GL_FLOAT, GL_NEAREST, GL_CLAMP_TO_EDGE, points.data());
61 | nextPositions.create(bufferWidth, bufferHeight, 1, GL_RGB32F, GL_RGB, GL_FLOAT, GL_NEAREST, GL_CLAMP_TO_EDGE, points.data());
62 | }
63 |
64 | void setup() {
65 | glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
66 |
67 | updateShader.vertexShader(glsl(
68 | in vec2 vertex;
69 | out vec2 coord;
70 | void main() {
71 | coord = vertex;
72 | gl_Position = vec4(vertex * 2.0 - 1.0, 0.0, 1.0);
73 | }
74 | )).fragmentShader(glsl(
75 | precision highp float;
76 | uniform sampler2D prevPositions;
77 | uniform sampler2D currPositions;
78 | uniform int bufferWidth;
79 | uniform int bufferHeight;
80 | in vec2 coord;
81 | out vec3 nextPosition;
82 | void main() {
83 | vec3 prevPosition = texture(prevPositions, coord).xyz;
84 | vec3 currPosition = texture(currPositions, coord).xyz;
85 | vec3 acceleration = vec3(0.0);
86 | for (int x = 0; x < bufferWidth; x++) {
87 | for (int y = 0; y < bufferHeight; y++) {
88 | vec3 position = texelFetch(currPositions, ivec2(x, y), 0).xyz;
89 | vec3 dir = position - currPosition;
90 | acceleration += dir / pow(dot(dir, dir) + 0.01, 1.5);
91 | }
92 | }
93 | nextPosition = 2 * currPosition - prevPosition + acceleration * 0.0000001;
94 | }
95 | )).link();
96 |
97 | drawShader.vertexShader(glsl(
98 | uniform int bufferWidth;
99 | uniform int bufferHeight;
100 | uniform sampler2D prevPositions;
101 | uniform sampler2D currPositions;
102 | uniform mat4 matrix;
103 | uniform mat4 modelview;
104 | uniform vec2 screenSize;
105 | out vec4 position;
106 | out float pointSize;
107 | out vec3 color;
108 | void main() {
109 | vec2 coord = vec2(
110 | float(gl_InstanceID % bufferWidth) / float(bufferWidth),
111 | float(gl_InstanceID / bufferWidth) / float(bufferHeight)
112 | );
113 | vec3 prevPosition = texture(prevPositions, coord).xyz;
114 | vec3 currPosition = texture(currPositions, coord).xyz;
115 | vec4 eyeSpace = modelview * vec4(currPosition, 1.0);
116 | position = gl_Position = matrix * vec4(currPosition, 1.0);
117 | pointSize = gl_PointSize = screenSize.y / length(eyeSpace) * 0.1;
118 |
119 | float t = length(currPosition - prevPosition);
120 | color = mix(vec3(0.5, 0.2, 1.0), vec3(1.0, 0.7, 0.2), t * 20.0);
121 | }
122 | )).fragmentShader(glsl(
123 | uniform vec2 screenSize;
124 | in vec4 position;
125 | in float pointSize;
126 | in vec3 color;
127 | out vec4 finalColor;
128 | void main() {
129 | vec2 screen = (0.5 + 0.5 * position.xy / position.w) * screenSize;
130 | float fade = clamp(pointSize * 0.5 - length(screen - gl_FragCoord.xy), 0.0, 1.0);
131 | finalColor = vec4(color * fade, 1.0);
132 | }
133 | )).link();
134 |
135 | accumulationShader.vertexShader(glsl(
136 | in vec2 vertex;
137 | out vec2 coord;
138 | void main() {
139 | coord = vertex;
140 | gl_Position = vec4(vertex * 2.0 - 1.0, 0.0, 1.0);
141 | }
142 | )).fragmentShader(glsl(
143 | uniform sampler2D renderTarget;
144 | in vec2 coord;
145 | out vec4 color;
146 | void main() {
147 | color = vec4(texture(renderTarget, coord).rgb, 0.01);
148 | }
149 | )).link();
150 |
151 | bokehFirstPass.vertexShader(glsl(
152 | in vec2 vertex;
153 | out vec2 coord;
154 | void main() {
155 | coord = vertex;
156 | gl_Position = vec4(vertex * 2.0 - 1.0, 0.0, 1.0);
157 | }
158 | )).fragmentShader(glsl(
159 | uniform sampler2D renderTarget;
160 | in vec2 coord;
161 | out vec4 colorA;
162 | out vec4 colorB;
163 | void main() {
164 | vec2 scale = 0.01 * vec2(dFdx(coord).x / dFdy(coord).y, 1.0);
165 | colorA = colorB = vec4(0.0);
166 | for (float t = 0.0; t <= 1.0; t += 1.0 / 32.0) {
167 | colorA += pow(texture(renderTarget, coord + vec2(0.0, 1.0) * t * scale), vec4(2.0));
168 | colorB += pow(texture(renderTarget, coord + vec2(-0.866025404, -0.5) * t * scale), vec4(2.0));
169 | }
170 | colorB = (colorA + colorB) / 66.0;
171 | colorA /= 33.0;
172 | }
173 | )).link();
174 |
175 | bokehSecondPass.vertexShader(glsl(
176 | in vec2 vertex;
177 | out vec2 coord;
178 | void main() {
179 | coord = vertex;
180 | gl_Position = vec4(vertex * 2.0 - 1.0, 0.0, 1.0);
181 | }
182 | )).fragmentShader(glsl(
183 | uniform sampler2D renderTargetA;
184 | uniform sampler2D renderTargetB;
185 | in vec2 coord;
186 | out vec4 color;
187 | void main() {
188 | vec2 scale = 0.01 * vec2(dFdx(coord).x / dFdy(coord).y, 1.0);
189 | color = vec4(0.0);
190 | for (float t = 0.0; t <= 1.0; t += 1.0 / 32.0) {
191 | color += texture(renderTargetA, coord + vec2(-0.866025404, -0.5) * t * scale);
192 | color += texture(renderTargetB, coord + vec2(0.866025404, -0.5) * t * scale) * 2.0;
193 | }
194 | color = pow(color / 99.0, vec4(0.5));
195 | }
196 | )).link();
197 |
198 | point << vec3();
199 | point.upload();
200 | pointLayout.create(drawShader, point).attribute("vertex", 3).check();
201 |
202 | quad << vec2(0, 0) << vec2(1, 0) << vec2(0, 1) << vec2(1, 1);
203 | quad.upload();
204 | quadLayout.create(updateShader, quad).attribute("vertex", 2).check();
205 |
206 | reset();
207 |
208 | drawShader.use();
209 | drawShader.uniformInt("bufferWidth", bufferWidth);
210 | drawShader.uniformInt("bufferHeight", bufferHeight);
211 | drawShader.uniformInt("prevPositions", 0);
212 | drawShader.uniformInt("currPositions", 1);
213 | drawShader.unuse();
214 |
215 | updateShader.use();
216 | updateShader.uniformInt("bufferWidth", bufferWidth);
217 | updateShader.uniformInt("bufferHeight", bufferHeight);
218 | updateShader.uniformInt("prevPositions", 0);
219 | updateShader.uniformInt("currPositions", 1);
220 | updateShader.unuse();
221 |
222 | bokehSecondPass.use();
223 | bokehSecondPass.uniformInt("renderTargetA", 0);
224 | bokehSecondPass.uniformInt("renderTargetB", 1);
225 | bokehSecondPass.unuse();
226 | }
227 |
228 | void draw() {
229 | // Set up the camera
230 | mat4 matrix, modelview;
231 | matrix.perspective(45, width / height, 0.01, 1000);
232 | modelview.translate(0, 0, -zoomZ).rotateX(angleX).rotateY(angleY);
233 | matrix *= modelview;
234 |
235 | if (postProcess) {
236 | fbo.attachColor(renderTarget).check();
237 | fbo.bind();
238 | }
239 |
240 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
241 | glEnable(GL_BLEND);
242 | glBlendFunc(GL_ONE_MINUS_DST_COLOR, GL_ONE);
243 | drawShader.use();
244 | drawShader.uniform("screenSize", vec2(width, height));
245 | drawShader.uniform("matrix", matrix);
246 | drawShader.uniform("modelview", modelview);
247 | prevPositions.bind(0);
248 | currPositions.bind(1);
249 | pointLayout.drawInstanced(currPositions.width * currPositions.height, GL_POINTS);
250 | currPositions.unbind(1);
251 | prevPositions.unbind(0);
252 | drawShader.unuse();
253 | glDisable(GL_BLEND);
254 |
255 | if (postProcess) {
256 | fbo.unbind();
257 |
258 | if (postProcess == Accumulation) {
259 | fbo.attachColor(accumulationTexture).check();
260 | fbo.bind();
261 | accumulationShader.use();
262 | renderTarget.bind();
263 | glEnable(GL_BLEND);
264 | glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
265 | quadLayout.draw(GL_TRIANGLE_STRIP);
266 | glDisable(GL_BLEND);
267 | renderTarget.unbind();
268 | accumulationShader.unuse();
269 | fbo.unbind();
270 |
271 | accumulationShader.use();
272 | accumulationTexture.bind();
273 | quadLayout.draw(GL_TRIANGLE_STRIP);
274 | accumulationTexture.unbind();
275 | accumulationShader.unuse();
276 | } else if (postProcess == Bokeh) {
277 | fbo.attachColor(bokehScratchA, 0).attachColor(bokehScratchB, 1).check();
278 | fbo.bind();
279 | bokehFirstPass.use();
280 | renderTarget.bind();
281 | quadLayout.draw(GL_TRIANGLE_STRIP);
282 | renderTarget.unbind();
283 | bokehFirstPass.unuse();
284 | fbo.unbind();
285 | fbo.detachColor(1);
286 |
287 | bokehSecondPass.use();
288 | bokehScratchA.bind(0);
289 | bokehScratchB.bind(1);
290 | quadLayout.draw(GL_TRIANGLE_STRIP);
291 | bokehScratchB.unbind(1);
292 | bokehScratchA.unbind(0);
293 | bokehSecondPass.unuse();
294 | }
295 | }
296 |
297 | glutSwapBuffers();
298 | }
299 |
300 | // For calculating mouse deltas
301 | int oldX, oldY;
302 |
303 | void mousemove(int x, int y) {
304 | angleY -= x - oldX;
305 | angleX -= y - oldY;
306 | oldX = x;
307 | oldY = y;
308 | angleX = fmaxf(-90, fminf(90, angleX));
309 | }
310 |
311 | void mousedown(int button, int, int x, int y) {
312 | if (button == 3) zoomZ /= 1.1;
313 | else if (button == 4) zoomZ *= 1.1;
314 | oldX = x;
315 | oldY = y;
316 | }
317 |
318 | void keydown(unsigned char key, int, int) {
319 | if (key == 27) exit(0);
320 | if (key == 'r' || key == 'R') reset();
321 | if (key == 'p' || key == 'P') paused = !paused;
322 | if (key == 'o' || key == 'O') postProcess = (PostProcess)((postProcess + 1) % PostProcessCount);
323 | }
324 |
325 | void update() {
326 | if (!paused) {
327 | fbo.attachColor(nextPositions).check();
328 |
329 | fbo.bind();
330 | updateShader.use();
331 | prevPositions.bind(0);
332 | currPositions.bind(1);
333 | quadLayout.draw(GL_TRIANGLE_STRIP);
334 | currPositions.unbind(1);
335 | prevPositions.unbind(0);
336 | updateShader.unuse();
337 | fbo.unbind();
338 |
339 | prevPositions.swapWith(currPositions);
340 | currPositions.swapWith(nextPositions);
341 | }
342 |
343 | draw();
344 | }
345 |
346 | void resize(int w, int h) {
347 | width = w;
348 | height = h;
349 | glViewport(0, 0, w, h);
350 | renderTarget.create(width, height, 1, GL_RGBA, GL_RGBA, GL_UNSIGNED_INT, GL_NEAREST, GL_CLAMP_TO_EDGE);
351 | accumulationTexture.create(width, height, 1, GL_RGBA32F, GL_RGBA, GL_FLOAT, GL_NEAREST, GL_CLAMP_TO_EDGE);
352 | bokehScratchA.create(width, height, 1, GL_RGBA32F, GL_RGBA, GL_FLOAT, GL_NEAREST, GL_CLAMP_TO_EDGE);
353 | bokehScratchB.create(width, height, 1, GL_RGBA32F, GL_RGBA, GL_FLOAT, GL_NEAREST, GL_CLAMP_TO_EDGE);
354 |
355 | fbo.attachColor(accumulationTexture).check();
356 | fbo.bind();
357 | glClear(GL_COLOR_BUFFER_BIT);
358 | fbo.unbind();
359 | }
360 |
361 | int main(int argc, char *argv[]) {
362 | glutInit(&argc, argv);
363 | glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
364 | glutCreateWindow("Example");
365 | glutReshapeWindow(width, height);
366 | glutDisplayFunc(draw);
367 | glutKeyboardFunc(keydown);
368 | glutMotionFunc(mousemove);
369 | glutMouseFunc(mousedown);
370 | glutReshapeFunc(resize);
371 | glutIdleFunc(update);
372 | setup();
373 | resize(width, height);
374 | glutMainLoop();
375 | return 0;
376 | }
377 |
--------------------------------------------------------------------------------
/proj4_fluid/Makefile:
--------------------------------------------------------------------------------
1 | build:
2 | g++ -I.. main.cpp ../gl4.cpp -lglut
3 |
--------------------------------------------------------------------------------
/proj4_fluid/README.md:
--------------------------------------------------------------------------------
1 | ## Project 4: SPH Fluid Simulation
2 |
3 | Controls:
4 |
5 | * Drag mouse: rotate camera
6 | * Mouse wheel: zoom camera
7 | * 1 through 4: different initial configurations
8 | * U: push all particles in a random direction
9 | * P: pause simulation
10 | * K: kill the velocity of all particles
11 | * E: explode the simulation
12 |
13 | ## Introduction
14 |
15 | This project implements a fluid simulation using lots of tiny spherical particles and a method called Smoothed Particle Hydrodynamics. It uses the simple O(n^2) algorithm that computes the force on each particle due to all other particles. However, the GPU can still simulate over 16,000 particles at real-time framerates.
16 |
17 | ## Implementation
18 |
19 | This was implemented using OpenGL 4 with Verlet integration. Under Verlet integration, the next position of the particle is calculated using only the previous two positions and the acceleration: next = 2 * current - previous + acceleration. I stored this information for all 16,384 particles in three 128x128 textures (for the previous, current, and next positions).
20 |
21 | The simulation was implemented using [Lagrangian Fluid Dynamics Using Smoothed Particle Hydrodynamics](http://image.diku.dk/projects/media/kelager.06.pdf) as a reference. Although the computations technically require a per-particle mass density to be computed as a separate pass before computing particle forces, re-using the mass density from the previous frame gave a noticable speedup and didn't have a visible effect on the simulation. The mass density is stored in the w-component of the position to avoid extra texture fetches in the update step.
22 |
23 | Particles are constrained to an inside-out cube and bounce off the sides with an elasticity of 0.5. I also added two additional volume types: upright cylinder and axis-aligned box.
24 |
25 | ## Rendering
26 |
27 | The particles were rendered using point primitives with a size inversely proportional to the distance from the camera. Points were rendered as spheres using the distance from the center of the point to gl_FragCoord. A per-pixel normal in world-space was reconstructed and used for top-down diffuse lighting. Screen-Space Ambient Occlusion (SSAO) was added as a second pass using the reconstructed eye-space normal.
28 |
--------------------------------------------------------------------------------
/proj4_fluid/main.cpp:
--------------------------------------------------------------------------------
1 | #include
2 | #include "gl4.h"
3 |
4 | const int bufferWidth = 128;
5 | const int bufferHeight = 128;
6 | const vec3 gridSize = vec3(0.5);
7 |
8 | bool paused = false;
9 | float accumulation = 0;
10 | float width = 800, height = 600;
11 | float angleX = -45, angleY = 45, zoomZ = length(gridSize) * 1.5;
12 |
13 | Buffer point;
14 | Buffer quad;
15 | VAO pointLayout;
16 | VAO quadLayout;
17 |
18 | Shader updateShader;
19 | Shader drawShader;
20 | FBO bufferFBO;
21 | FBO screenFBO;
22 |
23 | Texture prevPositions;
24 | Texture currPositions;
25 | Texture nextPositions;
26 |
27 | Texture positionDiffuseTexture;
28 | Texture normalTexture;
29 | Texture accumulationTextureA;
30 | Texture accumulationTextureB;
31 |
32 | Shader ssaoShader;
33 | Shader textureMappingShader;
34 |
35 | inline float frand() {
36 | return (float)rand() / (float)RAND_MAX;
37 | }
38 |
39 | enum Scene {
40 | RandomColumn,
41 | GridColumn,
42 | Drop,
43 | Top,
44 |
45 | SceneCount
46 | };
47 |
48 | void reset(Scene scene) {
49 | std::vector points;
50 | for (int i = 0; i < bufferWidth * bufferHeight; i++) {
51 | vec4 point;
52 | const int grid = 32;
53 | switch (scene) {
54 | case RandomColumn:
55 | point = vec4(frand() * 0.5, frand(), frand() * 0.5, 0);
56 | break;
57 | case GridColumn:
58 | point = vec4((i % grid) / float(grid - 1), (i / grid / grid) / float(grid - 1), (i / grid % grid) / float(grid - 1), 0);
59 | break;
60 | case Drop:
61 | if ((rand() & 0xF) == 0) {
62 | point = vec4(frand() * 0.25, frand() * 0.25 + 0.75, frand() * 0.25, 0);
63 | } else {
64 | point = vec4(frand(), frand() * 0.25, frand(), 0);
65 | }
66 | break;
67 | case Top:
68 | point = vec4(frand() * 0.5, frand() * 0.1 + 0.9, frand() * 0.5, 0);
69 | break;
70 | default:
71 | return;
72 | }
73 | point = point * vec4(gridSize, 1);
74 | point.w = 10000000;
75 | points.push_back(point);
76 | }
77 |
78 | prevPositions.create(bufferWidth, bufferHeight, 1, GL_RGBA32F, GL_RGBA, GL_FLOAT, GL_NEAREST, GL_CLAMP_TO_EDGE, points.data());
79 | currPositions.create(bufferWidth, bufferHeight, 1, GL_RGBA32F, GL_RGBA, GL_FLOAT, GL_NEAREST, GL_CLAMP_TO_EDGE, points.data());
80 | nextPositions.create(bufferWidth, bufferHeight, 1, GL_RGBA32F, GL_RGBA, GL_FLOAT, GL_NEAREST, GL_CLAMP_TO_EDGE, points.data());
81 |
82 | updateShader.use();
83 | updateShader.uniformInt("collideWithObjects", scene == Top);
84 | updateShader.unuse();
85 |
86 | accumulation = 0;
87 | }
88 |
89 | void setup() {
90 | glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
91 |
92 | updateShader.vertexShader(glsl(
93 | in vec2 vertex;
94 | out vec2 coord;
95 | void main() {
96 | coord = vertex;
97 | gl_Position = vec4(vertex * 2.0 - 1.0, 0.0, 1.0);
98 | }
99 | )).fragmentShader(glsl(
100 | precision highp float;
101 | uniform bool collideWithObjects;
102 | uniform vec3 gridSize;
103 | uniform sampler2D prevPositions;
104 | uniform sampler2D currPositions;
105 | uniform int bufferWidth;
106 | uniform int bufferHeight;
107 | in vec2 coord;
108 | out vec4 nextPosition;
109 |
110 | const float pi = 3.14159265;
111 |
112 | // The mass of an individual particle (affects mass density)
113 | const float particleMass = 1.0;
114 |
115 | // Particles will only affect other particles up to this distance
116 | const float maxRadius = 0.01;
117 |
118 | // Controls how viscous the fluid is
119 | const float viscosityCoefficient = 10.0;
120 |
121 | // A gas constant that depends on the temperature
122 | const float pressureConstant = 5.0e-6;
123 |
124 | // Rest density is a fake constant introduced by SPH for numerical stability
125 | const float restDensity = 1.0e-3;
126 |
127 | // The initial downward acceleration
128 | const float gravity = 9.8e-5;
129 |
130 | // Controls for surface tension
131 | const float surfaceTensionConstant = 2.0e-2;
132 | const float normalThreshold = 1000.0;
133 |
134 | // Assumes 0 <= radius <= maxRadius
135 | float massDensityKernel(float radius) {
136 | return 315.0 / (64.0 * pi * pow(maxRadius, 9.0)) * pow(pow(maxRadius, 2.0) - pow(radius, 2.0), 3.0);
137 | }
138 | float pressureKernel(float radius) {
139 | return -45.0 / (pi * pow(maxRadius, 6.0)) * pow(maxRadius - radius, 2.0);
140 | }
141 | float viscosityKernel(float radius) {
142 | return 45.0 / (pi * pow(maxRadius, 6.0)) * (maxRadius - radius);
143 | }
144 | float normalKernel(float radius) {
145 | return -945.0 / (32.0 * pi * pow(maxRadius, 9.0)) * radius * pow(pow(maxRadius, 2.0) - pow(radius, 2.0), 2.0);
146 | }
147 | float surfaceTensionKernel(float radius) {
148 | return -945.0 / (32.0 * pi * pow(maxRadius, 9.0)) * (pow(maxRadius, 2.0) - pow(radius, 2.0)) * (3.0 * pow(maxRadius, 2.0) - 7.0 * pow(radius, 2.0));
149 | }
150 |
151 | // Define the environment
152 | const float elasticity = 0.5;
153 |
154 | vec3 pushOutOfCylinder(vec3 oldPoint, vec3 newPoint, vec3 center, float radius, float height) {
155 | if (newPoint.y >= center.y && newPoint.y <= center.y + height) {
156 | vec2 delta = newPoint.xz - center.xz;
157 | if (length(delta) <= radius) {
158 | if (oldPoint.y <= center.y) {
159 | newPoint.y = min(center.y, oldPoint.y - abs(newPoint.y - oldPoint.y) * elasticity);
160 | } else if (oldPoint.y >= center.y + height) {
161 | newPoint.y = max(center.y + height, oldPoint.y + abs(newPoint.y - oldPoint.y) * elasticity);
162 | } else {
163 | vec3 normal = normalize(vec3(delta, 0.0).xzy);
164 | newPoint = oldPoint + reflect(newPoint - oldPoint, normal) * elasticity;
165 | newPoint.xz = center.xz + normal.xz * radius;
166 | }
167 | }
168 | }
169 | return newPoint;
170 | }
171 |
172 | vec3 pushOutOfBox(vec3 oldPoint, vec3 newPoint, vec3 minCoord, vec3 maxCoord) {
173 | vec3 clamped = clamp(newPoint, minCoord, maxCoord);
174 | if (clamped == newPoint) {
175 | vec3 delta = (oldPoint - minCoord) / (maxCoord - minCoord) - 0.5;
176 | vec3 choice = abs(delta);
177 | float largest = max(max(choice.x, choice.y), choice.z);
178 | if (choice.x == largest) {
179 | newPoint.x = oldPoint.x + (oldPoint.x - newPoint.x) * elasticity;
180 | newPoint.x = (delta.x > 0) ? max(newPoint.x, maxCoord.x) : min(newPoint.x, minCoord.x);
181 | } else if (choice.y == largest) {
182 | newPoint.y = oldPoint.y + (oldPoint.y - newPoint.y) * elasticity;
183 | newPoint.y = (delta.y > 0) ? max(newPoint.y, maxCoord.y) : min(newPoint.y, minCoord.y);
184 | } else {
185 | newPoint.z = oldPoint.z + (oldPoint.z - newPoint.z) * elasticity;
186 | newPoint.z = (delta.z > 0) ? max(newPoint.z, maxCoord.z) : min(newPoint.z, minCoord.z);
187 | }
188 | }
189 | return newPoint;
190 | }
191 |
192 | void main() {
193 | // Format is (x, y, z, massDensity)
194 | vec4 prevPosition = texture(prevPositions, coord);
195 | vec4 currPosition = texture(currPositions, coord);
196 |
197 | // Acceleration is initially due to gravity
198 | vec3 force = vec3(0.0, -currPosition.w * gravity, 0.0);
199 |
200 | // Calculate contributions from all pairs of particles
201 | float massDensity = 0.0;
202 | float surfaceTensionMagnitude = 0.0;
203 | vec3 normal = vec3(0.0);
204 | vec3 pressureForce = vec3(0.0);
205 | vec3 viscosityForce = vec3(0.0);
206 | for (int x = 0; x < bufferWidth; x++) {
207 | for (int y = 0; y < bufferHeight; y++) {
208 | vec4 currPairPosition = texelFetch(currPositions, ivec2(x, y), 0);
209 | vec3 delta = currPairPosition.xyz - currPosition.xyz;
210 | float distance = length(delta);
211 | if (distance < maxRadius) {
212 | massDensity += particleMass * massDensityKernel(distance);
213 | if (currPosition != currPairPosition) {
214 | vec3 unitDelta = delta / (distance + 0.000000001);
215 | float scale = particleMass / currPairPosition.w;
216 |
217 | // Compute pressure
218 | float meanPressure = pressureConstant * (currPosition.w + currPairPosition.w - 2.0 * restDensity) / 2.0;
219 | pressureForce += scale * meanPressure * unitDelta * pressureKernel(distance);
220 |
221 | // Compute viscosity
222 | vec4 prevPairPosition = texelFetch(prevPositions, ivec2(x, y), 0);
223 | vec3 velocityDelta = currPairPosition.xyz - prevPairPosition.xyz - currPosition.xyz + prevPosition.xyz;
224 | viscosityForce += scale * viscosityCoefficient * velocityDelta * viscosityKernel(distance);
225 |
226 | // Compute surface tension
227 | normal += scale * unitDelta * normalKernel(distance);
228 | surfaceTensionMagnitude += scale * surfaceTensionKernel(distance);
229 | }
230 | }
231 | }
232 | }
233 | force += pressureForce;
234 | force += viscosityForce;
235 | if (dot(normal, normal) > normalThreshold) {
236 | force -= surfaceTensionConstant * surfaceTensionMagnitude * normalize(normal);
237 | }
238 |
239 | // Move the point by the velocity. Division by zero (massDensity) is
240 | // avoided since the above loop calculates the non-zero contribution
241 | // from the current listener on itself.
242 | nextPosition.xyz = 2 * currPosition.xyz - prevPosition.xyz + force / massDensity;
243 | nextPosition.w = massDensity;
244 |
245 | // Start off with the new position
246 | vec3 oldPosition = currPosition.xyz / gridSize;
247 | vec3 newPosition = nextPosition.xyz / gridSize;
248 | vec3 velocity = newPosition - oldPosition;
249 |
250 | // Bounce off the walls of the box
251 | vec3 clamped = clamp(newPosition, 0.0, 1.0);
252 | if (clamped.x != newPosition.x) velocity.x = -velocity.x * elasticity;
253 | if (clamped.y != newPosition.y) velocity.y = -velocity.y * elasticity;
254 | if (clamped.z != newPosition.z) velocity.z = -velocity.z * elasticity;
255 |
256 | // Update the velocity
257 | newPosition = oldPosition + velocity;
258 |
259 | // Make sure we stay outside the objects in the box
260 | if (collideWithObjects) {
261 | newPosition = pushOutOfCylinder(oldPosition, newPosition, vec3(0.5, -1, 0.5), 0.25, 3);
262 | newPosition = pushOutOfBox(oldPosition, newPosition, vec3(-1, 0.8, 0.5), vec3(0.5, 1, 2));
263 | newPosition = pushOutOfBox(oldPosition, newPosition, vec3(-1, 0.7, -1), vec3(0.5, 0.9, 0.5));
264 | newPosition = pushOutOfBox(oldPosition, newPosition, vec3(0.5, 0.6, -1), vec3(2, 0.8, 0.5));
265 | newPosition = pushOutOfBox(oldPosition, newPosition, vec3(0.5, 0.5, 0.5), vec3(2, 0.7, 2));
266 | newPosition = pushOutOfBox(oldPosition, newPosition, vec3(-1, 0.4, 0.5), vec3(0.5, 0.6, 2));
267 | newPosition = pushOutOfBox(oldPosition, newPosition, vec3(-1, 0.3, -1), vec3(0.5, 0.5, 0.5));
268 | newPosition = pushOutOfBox(oldPosition, newPosition, vec3(0.5, 0.2, -1), vec3(2, 0.4, 0.5));
269 | newPosition = pushOutOfBox(oldPosition, newPosition, vec3(0.5, -1, 0.5), vec3(2, 0.3, 2));
270 | newPosition = pushOutOfBox(oldPosition, newPosition, vec3(-1, -1, 0.5), vec3(0.5, 0.2, 2));
271 | newPosition = pushOutOfBox(oldPosition, newPosition, vec3(-1, -1, -1), vec3(0.5, 0.1, 0.5));
272 | }
273 |
274 | // Make sure we stay inside the box
275 | nextPosition.xyz = clamp(newPosition, 0.0, 1.0) * gridSize;
276 | }
277 | )).link();
278 |
279 | drawShader.vertexShader(glsl(
280 | uniform int bufferWidth;
281 | uniform int bufferHeight;
282 | uniform sampler2D currPositions;
283 | uniform mat4 projection;
284 | uniform mat4 modelview;
285 | uniform vec2 screenSize;
286 | out vec4 position;
287 | out vec4 eyeSpace;
288 | out float pointSize;
289 | const float radius = 0.01;
290 | void main() {
291 | vec2 coord = vec2(
292 | float(gl_InstanceID % bufferWidth) / float(bufferWidth),
293 | float(gl_InstanceID / bufferWidth) / float(bufferHeight)
294 | );
295 | vec3 currPosition = texture(currPositions, coord).xyz;
296 | eyeSpace = modelview * vec4(currPosition, 1.0);
297 | position = gl_Position = projection * modelview * vec4(currPosition, 1.0);
298 | pointSize = gl_PointSize = screenSize.y / -eyeSpace.z * radius;
299 | }
300 | )).fragmentShader(glsl(
301 | uniform vec2 screenSize;
302 | uniform mat4 projection;
303 | uniform mat4 modelview;
304 | in vec4 position;
305 | in vec4 eyeSpace;
306 | in float pointSize;
307 | out vec4 positionDiffuse;
308 | out vec3 normal;
309 | const float radius = 0.01;
310 | void main() {
311 | vec2 screen = (0.5 + 0.5 * position.xy / position.w) * screenSize;
312 | vec2 delta = (screen - gl_FragCoord.xy) / pointSize * 2.0;
313 | if (length(delta) > 1.0) discard;
314 | vec3 eyeSpaceNormal = vec3(-delta.x, -delta.y, sqrt(1 - dot(delta, delta)));
315 | vec3 eyeSpacePos = eyeSpace.xyz + radius * eyeSpaceNormal;
316 | vec4 clipSpacePos = projection * vec4(eyeSpacePos, 1.0);
317 | vec3 worldSpaceNormal = normalize((vec4(eyeSpaceNormal, 0.0) * modelview).xyz);
318 | float diffuse = worldSpaceNormal.y * 0.5 + 0.5;
319 | positionDiffuse = vec4(eyeSpacePos, diffuse);
320 | normal = eyeSpaceNormal;
321 | gl_FragDepth = clipSpacePos.z / clipSpacePos.w;
322 | }
323 | )).link();
324 |
325 | ssaoShader.vertexShader(glsl(
326 | in vec2 vertex;
327 | out vec2 coord;
328 | void main() {
329 | coord = vertex;
330 | gl_Position = vec4(vertex * 2.0 - 1.0, 0.0, 1.0);
331 | }
332 | )).fragmentShader(glsl(
333 | uniform bool paused;
334 | uniform float accumulation;
335 | uniform float frame;
336 | uniform vec3 gridSize;
337 | uniform sampler2D positionDiffuseTexture;
338 | uniform sampler2D normalTexture;
339 | uniform sampler2D accumulationTexture;
340 | in vec2 coord;
341 | out vec4 color;
342 | float random(vec3 scale, float seed) {
343 | return fract(sin(dot(gl_FragCoord.xyz + seed, scale)) * 43758.5453 + seed);
344 | }
345 | float calcAO(vec2 offset, vec3 position, vec3 normal) {
346 | const float scale = 1.0;
347 | const float bias = -0.5;
348 | vec3 delta = texture(positionDiffuseTexture, coord + offset).xyz - position;
349 | vec3 direction = normalize(delta);
350 | float distance = length(delta) * scale;
351 | return max(0.0, dot(normal, direction) - bias) / (1.0 + distance * distance);
352 | }
353 | float ssao(vec3 position, vec3 normal) {
354 | const vec2[4] vectors = vec2[](vec2(-1.0, 0.0), vec2(1.0, 0.0), vec2(0.0, -1.0), vec2(0.0, 1.0));
355 | const int iterations = 4;
356 | float ao = 0.0;
357 | float radius = length(gridSize) * 0.05 / position.z;
358 | vec2 aspect = vec2(dFdx(coord).x / dFdy(coord).y, 1.0);
359 | for (int i = 0; i < iterations; i++) {
360 | const float pi = 3.14159265;
361 | const float invSqrt2 = 0.707106781;
362 | float angle = random(vec3(7878.34758, 8945.34, 2784.3758678) + position * 923.0 + normal * 3487.0, float(i) + frame) * pi * 2.0;
363 | vec2 randomVector = vec2(cos(angle), sin(angle));
364 | vec2 offset1 = reflect(vectors[i], randomVector) * radius;
365 | vec2 offset2 = vec2(dot(offset1, vec2(invSqrt2, -invSqrt2)), dot(offset1, vec2(invSqrt2)));
366 | ao += calcAO(aspect * offset1 * 0.25, position, normal);
367 | ao += calcAO(aspect * offset1 * 0.75, position, normal);
368 | ao += calcAO(aspect * offset2 * 0.5, position, normal);
369 | ao += calcAO(aspect * offset2, position, normal);
370 | }
371 | return 1.0 - ao / float(iterations) * 0.25;
372 | }
373 | void main() {
374 | vec4 positionDiffuse = texture(positionDiffuseTexture, coord);
375 | if (positionDiffuse == vec4(0.0)) {
376 | color = vec4(0.0);
377 | } else {
378 | vec3 normal = texture(normalTexture, coord).xyz;
379 | vec3 position = positionDiffuse.xyz;
380 | float ambient = ssao(position, normal);
381 | float diffuse = positionDiffuse.w;
382 | color = vec4(ambient * diffuse);
383 | }
384 | if (paused) {
385 | vec4 old = texture(accumulationTexture, coord);
386 | color = (color + old * accumulation) / (accumulation + 1);
387 | }
388 | }
389 | )).link();
390 |
391 | textureMappingShader.vertexShader(glsl(
392 | in vec2 vertex;
393 | out vec2 coord;
394 | void main() {
395 | coord = vertex;
396 | gl_Position = vec4(vertex * 2.0 - 1.0, 0.0, 1.0);
397 | }
398 | )).fragmentShader(glsl(
399 | uniform sampler2D data;
400 | in vec2 coord;
401 | out vec4 color;
402 | void main() {
403 | color = texture(data, coord);
404 | }
405 | )).link();
406 |
407 | point << vec3();
408 | point.upload();
409 | pointLayout.create(drawShader, point).attribute("vertex", 3).check();
410 |
411 | quad << vec2(0, 0) << vec2(1, 0) << vec2(0, 1) << vec2(1, 1);
412 | quad.upload();
413 | quadLayout.create(updateShader, quad).attribute("vertex", 2).check();
414 |
415 | reset(Top);
416 |
417 | drawShader.use();
418 | drawShader.uniformInt("bufferWidth", bufferWidth);
419 | drawShader.uniformInt("bufferHeight", bufferHeight);
420 | drawShader.unuse();
421 |
422 | updateShader.use();
423 | updateShader.uniform("gridSize", gridSize);
424 | updateShader.uniformInt("bufferWidth", bufferWidth);
425 | updateShader.uniformInt("bufferHeight", bufferHeight);
426 | updateShader.uniformInt("prevPositions", 0);
427 | updateShader.uniformInt("currPositions", 1);
428 | updateShader.unuse();
429 |
430 | ssaoShader.use();
431 | ssaoShader.uniform("gridSize", gridSize);
432 | ssaoShader.uniformInt("positionDiffuseTexture", 0);
433 | ssaoShader.uniformInt("normalTexture", 1);
434 | ssaoShader.uniformInt("accumulationTexture", 2);
435 | ssaoShader.unuse();
436 | }
437 |
438 | void draw() {
439 | // Set up the camera
440 | mat4 projection, modelview;
441 | projection.perspective(45, width / height, 0.01, 1000);
442 | modelview.translate(0, 0, -zoomZ).rotateX(angleX).rotateY(angleY).translate(-gridSize * vec3(0.5, 0.25, 0.5));
443 |
444 | screenFBO.attachColor(positionDiffuseTexture, 0).attachColor(normalTexture, 1).check();
445 | screenFBO.bind();
446 | glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
447 | glEnable(GL_DEPTH_TEST);
448 | drawShader.use();
449 | drawShader.uniform("screenSize", vec2(width, height));
450 | drawShader.uniform("projection", projection);
451 | drawShader.uniform("modelview", modelview);
452 | currPositions.bind();
453 | pointLayout.drawInstanced(currPositions.width * currPositions.height, GL_POINTS);
454 | currPositions.unbind();
455 | drawShader.unuse();
456 | glDisable(GL_DEPTH_TEST);
457 | screenFBO.detachColor(1).unbind();
458 |
459 | if (paused) {
460 | screenFBO.attachColor(accumulationTextureA).check();
461 | screenFBO.bind();
462 | accumulationTextureB.bind(2);
463 | }
464 |
465 | static float frame = 0;
466 | ssaoShader.use();
467 | ssaoShader.uniformFloat("accumulation", accumulation++);
468 | ssaoShader.uniformInt("paused", paused);
469 | ssaoShader.uniformFloat("frame", frame++);
470 | positionDiffuseTexture.bind(0);
471 | normalTexture.bind(1);
472 | quadLayout.draw(GL_TRIANGLE_STRIP);
473 | normalTexture.unbind(1);
474 | positionDiffuseTexture.unbind(0);
475 | ssaoShader.unuse();
476 |
477 | if (paused) {
478 | accumulationTextureB.unbind(2);
479 | screenFBO.unbind();
480 | accumulationTextureA.swapWith(accumulationTextureB);
481 |
482 | textureMappingShader.use();
483 | accumulationTextureA.bind();
484 | quadLayout.draw(GL_TRIANGLE_STRIP);
485 | accumulationTextureA.unbind();
486 | textureMappingShader.unuse();
487 | }
488 |
489 | glutSwapBuffers();
490 | }
491 |
492 | // For calculating mouse deltas
493 | int oldX, oldY;
494 |
495 | void mousemove(int x, int y) {
496 | angleY -= x - oldX;
497 | angleX -= y - oldY;
498 | oldX = x;
499 | oldY = y;
500 | angleX = fmaxf(-90, fminf(90, angleX));
501 | accumulation = 0;
502 | }
503 |
504 | void mousedown(int button, int, int x, int y) {
505 | if (button == 3) zoomZ /= 1.1;
506 | else if (button == 4) zoomZ *= 1.1;
507 | oldX = x;
508 | oldY = y;
509 | accumulation = 0;
510 | }
511 |
512 | void keydown(unsigned char key, int, int) {
513 | if (key == 27) exit(0);
514 | if (key >= '1' && key <= '1' + SceneCount) reset(Scene(key - '1'));
515 |
516 | if (key == 'k' || key == 'K') {
517 | std::vector data(bufferWidth * bufferHeight);
518 | currPositions.bind();
519 | glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT, &data[0]);
520 | currPositions.unbind();
521 | prevPositions.create(bufferWidth, bufferHeight, 1, GL_RGBA32F, GL_RGBA, GL_FLOAT, GL_NEAREST, GL_CLAMP_TO_EDGE, data.data());
522 | }
523 |
524 | if (key == 'p' || key == 'P') {
525 | paused = !paused;
526 | accumulation = 0;
527 | }
528 |
529 | if (key == 'u' || key == 'U') {
530 | std::vector data(bufferWidth * bufferHeight);
531 | currPositions.bind();
532 | glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT, &data[0]);
533 | currPositions.unbind();
534 | float angle = frand() * M_PI * 2;
535 | vec2 dir = vec2(cos(angle), sin(angle)) * 0.005;
536 | for (size_t i = 0; i < data.size(); i++) {
537 | vec4 &v = data[i];
538 | v.x += dir.x;
539 | v.z += dir.y;
540 | }
541 | prevPositions.create(bufferWidth, bufferHeight, 1, GL_RGBA32F, GL_RGBA, GL_FLOAT, GL_NEAREST, GL_CLAMP_TO_EDGE, data.data());
542 | }
543 |
544 | if (key == 'e' || key == 'E') {
545 | std::vector data(bufferWidth * bufferHeight);
546 | currPositions.bind();
547 | glGetTexImage(GL_TEXTURE_2D, 0, GL_RGBA, GL_FLOAT, &data[0]);
548 | currPositions.unbind();
549 | for (size_t i = 0; i < data.size(); i++) {
550 | vec4 &v = data[i];
551 | float theta = frand() * M_PI * 2;
552 | float phi = asinf(frand() * 2 - 1);
553 | vec3 dir = vec3(cos(theta) * cos(phi), sin(phi), sin(theta) * cos(phi)) * length(gridSize) * 0.025;
554 | v.x += dir.x;
555 | v.y += dir.y;
556 | v.z += dir.z;
557 | }
558 | prevPositions.create(bufferWidth, bufferHeight, 1, GL_RGBA32F, GL_RGBA, GL_FLOAT, GL_NEAREST, GL_CLAMP_TO_EDGE, data.data());
559 | }
560 | }
561 |
562 | void update() {
563 | if (!paused) {
564 | bufferFBO.attachColor(nextPositions).check();
565 | bufferFBO.bind();
566 | updateShader.use();
567 | prevPositions.bind(0);
568 | currPositions.bind(1);
569 | quadLayout.draw(GL_TRIANGLE_STRIP);
570 | currPositions.unbind(1);
571 | prevPositions.unbind(0);
572 | updateShader.unuse();
573 | bufferFBO.unbind();
574 |
575 | prevPositions.swapWith(currPositions);
576 | currPositions.swapWith(nextPositions);
577 | }
578 |
579 | draw();
580 | }
581 |
582 | void resize(int w, int h) {
583 | width = w;
584 | height = h;
585 | glViewport(0, 0, w, h);
586 |
587 | std::vector zero(w * h * 3);
588 | positionDiffuseTexture.create(w, h, 1, GL_RGBA32F, GL_RGBA, GL_FLOAT, GL_NEAREST, GL_CLAMP_TO_EDGE);
589 | normalTexture.create(w, h, 1, GL_RGB32F, GL_RGB, GL_FLOAT, GL_NEAREST, GL_CLAMP_TO_EDGE);
590 | accumulationTextureA.create(w, h, 1, GL_RGB32F, GL_RGB, GL_FLOAT, GL_NEAREST, GL_CLAMP_TO_EDGE, zero.data());
591 | accumulationTextureB.create(w, h, 1, GL_RGB32F, GL_RGB, GL_FLOAT, GL_NEAREST, GL_CLAMP_TO_EDGE, zero.data());
592 | accumulation = 0;
593 | }
594 |
595 | int main(int argc, char *argv[]) {
596 | glutInit(&argc, argv);
597 | glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
598 | glutCreateWindow("Example");
599 | glutReshapeWindow(width, height);
600 | glutDisplayFunc(draw);
601 | glutKeyboardFunc(keydown);
602 | glutMotionFunc(mousemove);
603 | glutMouseFunc(mousedown);
604 | glutReshapeFunc(resize);
605 | glutIdleFunc(update);
606 | setup();
607 | resize(width, height);
608 | glutMainLoop();
609 | return 0;
610 | }
611 |
--------------------------------------------------------------------------------