Created
June 5, 2019 17:48
-
-
Save mstange/41e89dba2030e43511ee1784ccc44f56 to your computer and use it in GitHub Desktop.
Example program that renders feTurbulence using OpenGL shaders
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Produces rendering that looks like http://tests.themasta.com/turbulence.svg | |
// | |
// Save to main.mm, and then compile and run using: | |
// $ clang++ main.mm -Wall -O3 -framework Cocoa -framework OpenGL -o test && ./test | |
// | |
// Equivalent SVG code: | |
// | |
// <svg xmlns="http://www.w3.org/2000/svg"> | |
// <defs> | |
// <filter id="turb" color-interpolation-filters="sRGB"> | |
// <feTurbulence type="fractalNoise" baseFrequency="0.01 .01" numOctaves="5"/> | |
// </filter> | |
// </defs> | |
// <rect width="100%" height="100%" filter="url(#turb)"/> | |
// </svg> | |
#import <Cocoa/Cocoa.h> | |
#include <OpenGL/gl.h> | |
#define checkError() | |
class ShaderProgram | |
{ | |
public: | |
ShaderProgram(const char* vertexShader, const char* fragmentShader); | |
virtual ~ShaderProgram() {} | |
virtual void Use() { glUseProgram(mProgramID); } | |
virtual void DrawQuad(GLuint aQuad); | |
protected: | |
GLuint mProgramID; | |
GLuint mPosAttribute; | |
}; | |
static GLuint | |
CompileShaders(const char* vertexShader, const char* fragmentShader) | |
{ | |
// Create the shaders | |
GLuint vertexShaderID = glCreateShader(GL_VERTEX_SHADER); | |
GLuint fragmentShaderID = glCreateShader(GL_FRAGMENT_SHADER); | |
GLint result = GL_FALSE; | |
int infoLogLength; | |
// Compile Vertex Shader | |
glShaderSource(vertexShaderID, 1, &vertexShader , NULL); | |
glCompileShader(vertexShaderID); | |
// Check Vertex Shader | |
glGetShaderiv(vertexShaderID, GL_COMPILE_STATUS, &result); | |
glGetShaderiv(vertexShaderID, GL_INFO_LOG_LENGTH, &infoLogLength); | |
if (infoLogLength > 0) { | |
char* vertexShaderErrorMessage = new char[infoLogLength+1]; | |
glGetShaderInfoLog(vertexShaderID, infoLogLength, NULL, vertexShaderErrorMessage); | |
printf("%s\n", vertexShaderErrorMessage); | |
delete[] vertexShaderErrorMessage; | |
} | |
// Compile Fragment Shader | |
glShaderSource(fragmentShaderID, 1, &fragmentShader , NULL); | |
glCompileShader(fragmentShaderID); | |
// Check Fragment Shader | |
glGetShaderiv(fragmentShaderID, GL_COMPILE_STATUS, &result); | |
glGetShaderiv(fragmentShaderID, GL_INFO_LOG_LENGTH, &infoLogLength); | |
if (infoLogLength > 0) { | |
char* fragmentShaderErrorMessage = new char[infoLogLength+1]; | |
glGetShaderInfoLog(fragmentShaderID, infoLogLength, NULL, fragmentShaderErrorMessage); | |
printf("%s\n", fragmentShaderErrorMessage); | |
delete[] fragmentShaderErrorMessage; | |
} | |
// Link the program | |
GLuint programID = glCreateProgram(); | |
glAttachShader(programID, vertexShaderID); | |
glAttachShader(programID, fragmentShaderID); | |
glLinkProgram(programID); | |
// Check the program | |
glGetProgramiv(programID, GL_LINK_STATUS, &result); | |
glGetProgramiv(programID, GL_INFO_LOG_LENGTH, &infoLogLength); | |
if (infoLogLength > 0) { | |
char* programErrorMessage = new char[infoLogLength+1]; | |
glGetProgramInfoLog(programID, infoLogLength, NULL, programErrorMessage); | |
printf("%s\n", programErrorMessage); | |
delete[] programErrorMessage; | |
} | |
glDeleteShader(vertexShaderID); | |
glDeleteShader(fragmentShaderID); | |
return programID; | |
} | |
ShaderProgram::ShaderProgram(const char* vertexShader, const char* fragmentShader) | |
{ | |
mProgramID = CompileShaders(vertexShader, fragmentShader); | |
mPosAttribute = glGetAttribLocation(mProgramID, "aPos"); | |
} | |
void | |
ShaderProgram::DrawQuad(GLuint aQuad) | |
{ | |
glEnableVertexAttribArray(mPosAttribute); | |
glBindBuffer(GL_ARRAY_BUFFER, aQuad); | |
glVertexAttribPointer( | |
mPosAttribute, // The attribute we want to configure | |
2, // size | |
GL_FLOAT, // type | |
GL_FALSE, // normalized? | |
0, // stride | |
(void*)0 // array buffer offset | |
); | |
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); | |
glDisableVertexAttribArray(mPosAttribute); | |
} | |
class TurbulenceProgram : public ShaderProgram | |
{ | |
public: | |
TurbulenceProgram(); | |
virtual ~TurbulenceProgram(); | |
void SetSeed(int32_t aSeed); | |
void SetBaseFrequency(CGSize aBaseFreq); | |
void SetOffset(CGPoint aOffset); | |
protected: | |
GLuint mLatticeSelectorTexture; | |
GLuint mLatticeSelectorUniform; | |
GLuint mGradientTexture; | |
GLuint mGradientUniform; | |
GLuint mBaseFrequencyUniform; | |
GLuint mOffsetUniform; | |
int32_t mSeed; | |
}; | |
static const char* sCoverBufferWith01aPosVertexShader = | |
"#version 120\n" | |
"// Input vertex data, different for all executions of this shader.\n" | |
"attribute vec2 aPos;\n" | |
"varying vec2 vPos;\n" | |
"void main(){\n" | |
" vPos = aPos;\n" | |
" gl_Position = vec4(aPos * 2.0 - vec2(1.0), 0.0, 1.0);\n" | |
"}\n"; | |
static const char* sTurbulenceFragmentShader = | |
"#version 120\n" | |
"varying vec2 vPos;\n" | |
"uniform sampler1D uLatticeSelector;\n" | |
"uniform sampler1D uGradient;\n" | |
"uniform vec2 uBaseFrequency;\n" | |
"uniform vec2 uOffset;\n" | |
"vec2 SCurve(vec2 t)\n" | |
"{\n" | |
" return t * t * (3 - 2 * t);\n" | |
"}\n" | |
"\n" | |
"vec4 BiLerp(vec2 t, vec4 aa, vec4 ab, vec4 ba, vec4 bb)\n" | |
"{\n" | |
" return mix(mix(aa, ab, t.x), mix(ba, bb, t.x), t.y);\n" | |
"}\n" | |
"\n" | |
"\n" | |
"vec4 Interpolate(vec2 r, vec4 qua0, vec4 qua1, vec4 qub0, vec4 qub1, vec4 qva0, vec4 qva1, vec4 qvb0, vec4 qvb1)\n" | |
"{\n" | |
" return BiLerp(SCurve(r),\n" | |
" qua0 * r.x + qua1 * r.y,\n" | |
" qva0 * (r.x - 1) + qva1 * r.y,\n" | |
" qub0 * r.x + qub1 * (r.y - 1),\n" | |
" qvb0 * (r.x - 1) + qvb1 * (r.y - 1));\n" | |
"}\n" | |
"\n" | |
"vec4 Noise2(vec2 pos)\n" | |
"{\n" | |
" vec2 nearestLatticePoint = floor(pos);\n" | |
" vec2 fractionalOffset = pos - nearestLatticePoint;\n" | |
" vec2 b0 = nearestLatticePoint; // + 4096\n;" | |
" vec2 b1 = b0 + 1;\n" | |
" float i = texture1D(uLatticeSelector, b0.x / 256.0).b * 255;\n" | |
" float j = texture1D(uLatticeSelector, b1.x / 256.0).b * 255;\n" | |
" vec4 qua0 = texture1D(uGradient, (i + b0.y) / 256);\n" | |
" vec4 qua1 = texture1D(uGradient, (i + b0.y) / 256 + 1.0 / 512);\n" | |
" vec4 qub0 = texture1D(uGradient, (i + b1.y) / 256);\n" | |
" vec4 qub1 = texture1D(uGradient, (i + b1.y) / 256 + 1.0 / 512);\n" | |
" vec4 qva0 = texture1D(uGradient, (j + b0.y) / 256);\n" | |
" vec4 qva1 = texture1D(uGradient, (j + b0.y) / 256 + 1.0 / 512);\n" | |
" vec4 qvb0 = texture1D(uGradient, (j + b1.y) / 256);\n" | |
" vec4 qvb1 = texture1D(uGradient, (j + b1.y) / 256 + 1.0 / 512);\n" | |
" return Interpolate(fractionalOffset, qua0, qua1, qub0, qub1, qva0, qva1, qvb0, qvb1);\n" | |
"}\n" | |
"\n" | |
"void main()\n" | |
"{\n" | |
" int numOctaves = 6;\n" | |
" vec2 pos = (vec2(vPos.x, 1 - vPos.y) - uOffset) * uBaseFrequency;\n" | |
" float ratio = 1.0;\n" | |
" vec4 result = vec4(0, 0, 0, 0);\n" | |
" for (int i = 0; i < numOctaves; i++) {\n" | |
" result += Noise2(pos) / ratio;\n" | |
" pos *= 2;\n" | |
" ratio *= 2;\n" | |
" }\n" | |
" gl_FragColor = (result + vec4(1)) / 2;\n" | |
"}\n"; | |
TurbulenceProgram::TurbulenceProgram() | |
: ShaderProgram(sCoverBufferWith01aPosVertexShader, sTurbulenceFragmentShader) | |
, mLatticeSelectorTexture(0) | |
, mGradientTexture(0) | |
, mSeed(0) | |
{ | |
mLatticeSelectorUniform = glGetUniformLocation(mProgramID, "uLatticeSelector"); | |
mGradientUniform = glGetUniformLocation(mProgramID, "uGradient"); | |
mBaseFrequencyUniform = glGetUniformLocation(mProgramID, "uBaseFrequency"); | |
mOffsetUniform = glGetUniformLocation(mProgramID, "uOffset"); | |
checkError(); | |
} | |
TurbulenceProgram::~TurbulenceProgram() | |
{ | |
glDeleteTextures(1, &mLatticeSelectorTexture); | |
glDeleteTextures(1, &mGradientTexture); | |
checkError(); | |
} | |
namespace { | |
struct RandomNumberSource | |
{ | |
RandomNumberSource(int32_t aSeed) : mLast(SetupSeed(aSeed)) {} | |
int32_t Next() { mLast = Random(mLast); return mLast; } | |
private: | |
static const int32_t RAND_M = 2147483647; /* 2**31 - 1 */ | |
static const int32_t RAND_A = 16807; /* 7**5; primitive root of m */ | |
static const int32_t RAND_Q = 127773; /* m / a */ | |
static const int32_t RAND_R = 2836; /* m % a */ | |
/* Produces results in the range [1, 2**31 - 2]. | |
Algorithm is: r = (a * r) mod m | |
where a = 16807 and m = 2**31 - 1 = 2147483647 | |
See [Park & Miller], CACM vol. 31 no. 10 p. 1195, Oct. 1988 | |
To test: the algorithm should produce the result 1043618065 | |
as the 10,000th generated number if the original seed is 1. | |
*/ | |
static int32_t | |
SetupSeed(int32_t aSeed) { | |
if (aSeed <= 0) | |
aSeed = -(aSeed % (RAND_M - 1)) + 1; | |
if (aSeed > RAND_M - 1) | |
aSeed = RAND_M - 1; | |
return aSeed; | |
} | |
static int32_t | |
Random(int32_t aSeed) | |
{ | |
int32_t result = RAND_A * (aSeed % RAND_Q) - RAND_R * (aSeed / RAND_Q); | |
if (result <= 0) | |
result += RAND_M; | |
return result; | |
} | |
int32_t mLast; | |
}; | |
} // unnamed namespace | |
const static int sBSize = 0x100; | |
template<typename T> | |
static void | |
Swap(T& a, T& b) { | |
T c = a; | |
a = b; | |
b = c; | |
} | |
void | |
TurbulenceProgram::SetSeed(int32_t aSeed) | |
{ | |
if (aSeed != mSeed || !mLatticeSelectorTexture || !mGradientTexture) { | |
RandomNumberSource rand(aSeed); | |
uint32_t mLatticeSelector[sBSize]; | |
float mGradient[sBSize][2][4]; | |
float gradient[4][sBSize][2]; | |
for (int32_t k = 0; k < 4; k++) { | |
for (int32_t i = 0; i < sBSize; i++) { | |
float a = float((rand.Next() % (sBSize + sBSize)) - sBSize) / sBSize; | |
float b = float((rand.Next() % (sBSize + sBSize)) - sBSize) / sBSize; | |
float s = sqrt(a * a + b * b); | |
gradient[k][i][0] = a / s; | |
gradient[k][i][1] = b / s; | |
} | |
} | |
for (int32_t i = 0; i < sBSize; i++) { | |
mLatticeSelector[i] = i; | |
} | |
for (int32_t i1 = sBSize - 1; i1 > 0; i1--) { | |
int32_t i2 = rand.Next() % sBSize; | |
Swap(mLatticeSelector[i1], mLatticeSelector[i2]); | |
} | |
for (int32_t i = 0; i < sBSize; i++) { | |
uint8_t j = mLatticeSelector[i]; | |
mGradient[i][0][0] = gradient[0][j][0]; | |
mGradient[i][0][1] = gradient[1][j][0]; | |
mGradient[i][0][2] = gradient[2][j][0]; | |
mGradient[i][0][3] = gradient[3][j][0]; | |
mGradient[i][1][0] = gradient[0][j][1]; | |
mGradient[i][1][1] = gradient[1][j][1]; | |
mGradient[i][1][2] = gradient[2][j][1]; | |
mGradient[i][1][3] = gradient[3][j][1]; | |
} | |
glActiveTexture(GL_TEXTURE0); | |
if (!mLatticeSelectorTexture) { | |
glGenTextures(1, &mLatticeSelectorTexture); | |
} | |
glBindTexture(GL_TEXTURE_1D, mLatticeSelectorTexture); | |
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA, 256, 0, GL_BGRA, GL_UNSIGNED_BYTE, mLatticeSelector); | |
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
checkError(); | |
glActiveTexture(GL_TEXTURE1); | |
if (!mGradientTexture) { | |
glGenTextures(1, &mGradientTexture); | |
} | |
glBindTexture(GL_TEXTURE_1D, mGradientTexture); | |
glTexImage1D(GL_TEXTURE_1D, 0, GL_RGBA32F_ARB, 512, 0, GL_RGBA, GL_FLOAT, mGradient); | |
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | |
glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | |
checkError(); | |
} | |
glActiveTexture(GL_TEXTURE0); | |
glBindTexture(GL_TEXTURE_1D, mLatticeSelectorTexture); | |
glUniform1i(mLatticeSelectorUniform, 0); | |
checkError(); | |
glActiveTexture(GL_TEXTURE1); | |
glBindTexture(GL_TEXTURE_1D, mGradientTexture); | |
glUniform1i(mGradientUniform, 1); | |
checkError(); | |
} | |
void | |
TurbulenceProgram::SetBaseFrequency(CGSize aBaseFreq) | |
{ | |
glUniform2f(mBaseFrequencyUniform, aBaseFreq.width, aBaseFreq.height); | |
} | |
void | |
TurbulenceProgram::SetOffset(CGPoint aOffset) | |
{ | |
glUniform2f(mOffsetUniform, aOffset.x, aOffset.y); | |
} | |
@interface TestView: NSView | |
{ | |
NSOpenGLContext* mContext; | |
TurbulenceProgram* mTurbulenceProgram; | |
GLuint mQuad; | |
NSOpenGLPixelFormat* mPixelFormat; | |
CGPoint mScrollOffset; | |
} | |
@end | |
@implementation TestView | |
- (id)initWithFrame:(NSRect)aFrame | |
{ | |
if (self = [super initWithFrame:aFrame]) { | |
NSOpenGLPixelFormatAttribute attribs[] = { | |
NSOpenGLPFAAccelerated, | |
NSOpenGLPFADoubleBuffer, | |
(NSOpenGLPixelFormatAttribute)nil | |
}; | |
mPixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:attribs]; | |
mContext = [[NSOpenGLContext alloc] initWithFormat:mPixelFormat shareContext:nil]; | |
GLint swapInt = 1; | |
[mContext setValues:&swapInt forParameter:NSOpenGLCPSwapInterval]; | |
GLint opaque = 0; | |
[mContext setValues:&opaque forParameter:NSOpenGLCPSurfaceOpacity]; | |
[mContext makeCurrentContext]; | |
[self _initGL]; | |
[[NSNotificationCenter defaultCenter] addObserver:self | |
selector:@selector(_surfaceNeedsUpdate:) | |
name:NSViewGlobalFrameDidChangeNotification | |
object:self]; | |
} | |
return self; | |
} | |
- (void)dealloc | |
{ | |
[self _cleanupGL]; | |
[mPixelFormat release]; | |
[mContext release]; | |
[super dealloc]; | |
} | |
- (void)_initGL | |
{ | |
mTurbulenceProgram = new TurbulenceProgram(); | |
mQuad = [self _createQuad]; | |
} | |
- (BOOL)isFlipped | |
{ | |
return YES; | |
} | |
- (GLuint)_createQuad | |
{ | |
static const GLfloat data[] = { | |
0.0f, 0.0f, | |
1.0f, 0.0f, | |
0.0f, 1.0f, | |
1.0f, 1.0f | |
}; | |
GLuint quad; | |
glGenBuffers(1, &quad); | |
glBindBuffer(GL_ARRAY_BUFFER, quad); | |
glBufferData(GL_ARRAY_BUFFER, sizeof(data), data, GL_STATIC_DRAW); | |
checkError(); | |
return quad; | |
} | |
- (void)_cleanupGL | |
{ | |
glDeleteBuffers(1, &mQuad); | |
delete mTurbulenceProgram; | |
} | |
- (void)_surfaceNeedsUpdate:(NSNotification*)notification | |
{ | |
[mContext update]; | |
} | |
- (void)drawRect:(NSRect)aRect | |
{ | |
GLdouble pointWidth = [self bounds].size.width; | |
GLdouble pointHeight = [self bounds].size.height; | |
NSSize backingSize = [self convertSizeToBacking:[self bounds].size]; | |
GLdouble width = backingSize.width; | |
GLdouble height = backingSize.height; | |
[mContext setView:self]; | |
[mContext makeCurrentContext]; | |
checkError(); | |
glViewport(0, 0, width, height); | |
checkError(); | |
glClearColor(1.0, 1.0, 1.0, 1.0); | |
glClear(GL_COLOR_BUFFER_BIT); | |
checkError(); | |
// Turbulence | |
checkError(); | |
glEnable(GL_BLEND); | |
// Unpremultiplied operator source over | |
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, | |
GL_ONE, GL_ONE_MINUS_SRC_ALPHA); | |
mTurbulenceProgram->Use(); | |
mTurbulenceProgram->SetSeed(0); | |
mTurbulenceProgram->SetBaseFrequency(CGSizeMake(pointWidth / 100, pointHeight / 100)); | |
mTurbulenceProgram->SetOffset(CGPointMake(mScrollOffset.x / pointWidth, mScrollOffset.y / pointHeight)); | |
mTurbulenceProgram->DrawQuad(mQuad); | |
glDisable(GL_BLEND); | |
[mContext flushBuffer]; | |
} | |
- (void)scrollWheel:(NSEvent*)event | |
{ | |
mScrollOffset.x += [event scrollingDeltaX]; | |
mScrollOffset.y += [event scrollingDeltaY]; | |
[self setNeedsDisplay:YES]; | |
} | |
- (BOOL)wantsBestResolutionOpenGLSurface | |
{ | |
return YES; | |
} | |
@end | |
@interface TerminateOnClose : NSObject<NSWindowDelegate> | |
@end | |
@implementation TerminateOnClose | |
- (void)windowWillClose:(NSNotification*)notification | |
{ | |
[NSApp terminate:self]; | |
} | |
@end | |
int | |
main (int argc, char **argv) | |
{ | |
NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; | |
[NSApplication sharedApplication]; | |
[NSApp setActivationPolicy:NSApplicationActivationPolicyRegular]; | |
int style = | |
NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskResizable | NSWindowStyleMaskMiniaturizable; | |
NSRect contentRect = NSMakeRect(200, 200, 1000, 625); | |
NSWindow* window = [[NSWindow alloc] initWithContentRect:contentRect | |
styleMask:style | |
backing:NSBackingStoreBuffered | |
defer:NO]; | |
NSView* view = [[TestView alloc] initWithFrame:NSMakeRect(0, 0, contentRect.size.width, contentRect.size.height)]; | |
[window setContentView:view]; | |
[window setDelegate:[[TerminateOnClose alloc] autorelease]]; | |
[window setCollectionBehavior:[window collectionBehavior] | NSWindowCollectionBehaviorFullScreenPrimary]; | |
[NSApp activateIgnoringOtherApps:YES]; | |
[window makeKeyAndOrderFront:window]; | |
[NSApp run]; | |
[pool release]; | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment