-
Notifications
You must be signed in to change notification settings - Fork 69
/
shader_noise.py
194 lines (169 loc) · 5.64 KB
/
shader_noise.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
"""shader_noise shader function and texture generator
as described in "GPU Gems" chapter 5:
http://http.developer.nvidia.com/GPUGems/gpugems_ch05.html
"""
__version__ = "$Id: shader_noise.py 37 2008-06-27 22:25:39Z casey.duncan $"
from noise import pnoise3
import ctypes
from pyglet.gl import *
class ShaderNoiseTexture:
"""tiling 3D noise texture with two channels for use by the
shader noise functions.
"""
def __init__(self, freq=8, width=32):
"""Generate the 3D noise texture.
freq -- frequency of generated noise over the width of the
texture.
width -- Width of the texture in texels. The texture is cubic,
thus all sides are the same width. Must be a power of two.
Using a larger width can reduce artifacts caused by linear
interpolation of the noise texture, at the cost of video
memory, and possibly slower texture access.
"""
self.freq = freq
self.width = width
scale = float(freq) / width
width2 = width**2
texel = (ctypes.c_ushort * (2 * width**3))()
for z in range(width):
for y in range(width):
for x in range(width):
texel[(x + (y * width) + (z * width2)) * 2] = int((pnoise3(
x * scale, y * scale, z * scale,
repeatx=freq, repeaty=freq, repeatz=freq) + 1.0) * 32767)
texel[(x + (y * width) + (z * width2)) * 2 + 1] = int((pnoise3(
x * scale, y * scale, z * scale,
repeatx=freq, repeaty=freq, repeatz=freq, base=freq + 1) + 1.0) * 32767)
self.data = texel
def load(self):
"""Load the noise texture data into the current texture unit"""
glTexImage3D(GL_TEXTURE_3D, 0, GL_LUMINANCE16_ALPHA16,
self.width, self.width, self.width, 0, GL_LUMINANCE_ALPHA,
GL_UNSIGNED_SHORT, ctypes.byref(self.data))
def enable(self):
"""Convenience method to enable 3D texturing state so the texture may be used by the
ffpnoise shader function
"""
glEnable(GL_TEXTURE_3D)
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_S, GL_REPEAT)
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_T, GL_REPEAT)
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_WRAP_R, GL_REPEAT)
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_3D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
shader_noise_glsl = '''
/*
* GLSL Shader functions for fast fake Perlin 3D noise
*
* The required shader_noise_tex texture can be generated using the
* ShaderNoiseTexture class. It is a toroidal tiling 3D texture with each texel
* containing two 16-bit noise source channels. The shader permutes the source
* texture values by combining the channels such that the noise repeats at a
* much larger interval than the input texture.
*/
uniform sampler3D shader_noise_tex;
const float twopi = 3.1415926 * 2.0;
/* Simple perlin noise work-alike */
float
pnoise(vec3 position)
{
vec4 hi = 2.0 * texture3D(shader_noise_tex, position.xyz) - 1.0;
vec4 lo = 2.0 * texture3D(shader_noise_tex, position.xyz / 9.0) - 1.0;
return hi.r * cos(twopi * lo.r) + hi.a * sin(twopi * lo.r);
}
/* Multi-octave fractal brownian motion perlin noise */
float
fbmnoise(vec3 position, int octaves)
{
float m = 1.0;
vec3 p = position;
vec4 hi = vec4(0.0);
/* XXX Loops may not work correctly on all video cards */
for (int x = 0; x < octaves; x++) {
hi += (2.0 * texture3D(shader_noise_tex, p.xyz) - 1.0) * m;
p *= 2.0;
m *= 0.5;
}
vec4 lo = 2.0 * texture3D(shader_noise_tex, position.xyz / 9.0) - 1.0;
return hi.r * cos(twopi * lo.r) + hi.a * sin(twopi * lo.r);
}
/* Multi-octave turbulent noise */
float
fbmturbulence(vec3 position, int octaves)
{
float m = 1.0;
vec3 p = position;
vec4 hi = vec4(0.0);
/* XXX Loops may not work correctly on all video cards */
for (int x = 0; x < octaves; x++) {
hi += abs(2.0 * texture3D(shader_noise_tex, p.xyz) - 1.0) * m;
p *= 2.0;
m *= 0.5;
}
vec4 lo = texture3D(shader_noise_tex, position.xyz / 9.0);
return 2.0 * mix(hi.r, hi.a, cos(twopi * lo.r) * 0.5 + 0.5) - 1.0;
}
'''
if __name__ == '__main__':
# Demo using a simple noise-textured rotating sphere
import shader
win = pyglet.window.Window(width=640, height=640, resizable=True, visible=False)
vert_shader = shader.VertexShader('stupid', '''
/* simple vertex shader that stores the vertex position in a varying
* for easy access by the frag shader
*/
varying vec3 position;
void main(void) {
position = gl_Vertex.xyz * 5.0;
gl_Position = ftransform();
}
''')
frag_shader = shader.FragmentShader('noise_test', shader_noise_glsl + '''
varying vec3 position;
void main(void) {
float v;
float a = atan(position.y, position.x);
float arc = 3.14159 / 3.0;
if (a > -arc && a < arc) {
v = pnoise(position) * 0.5 + 0.5;
} else if (a > arc && a < arc * 4.0) {
v = fbmnoise(position, 4) * 0.5 + 0.5;
} else {
v = fbmturbulence(position, 4) * 0.5 + 0.5;
}
gl_FragColor = vec4(v, v, v, 1.0);
}
''')
shader_prog = shader.ShaderProgram(vert_shader, frag_shader)
shader_prog.install()
tex = ShaderNoiseTexture()
tex.load()
tex.enable()
shader_prog.uset1I('shader_noise_tex', 0)
quadratic = gluNewQuadric()
gluQuadricNormals(quadratic, GLU_SMOOTH)
gluQuadricTexture(quadratic, GL_TRUE)
glEnable(GL_CULL_FACE)
global spin
spin = 0
def on_resize(width, height):
glViewport(0, 0, width, height)
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
gluPerspective(70, 1.0*width/height, 0.1, 1000.0)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
win.on_resize = on_resize
@win.event
def on_draw():
global spin
win.clear()
glLoadIdentity()
glTranslatef(0, 0, -1.5)
glRotatef(spin, 1.0, 1.0, 1.0)
gluSphere(quadratic, 0.65, 60, 60)
def update(dt):
global spin
spin += dt * 10.0
pyglet.clock.schedule_interval(update, 1.0/30.0)
win.set_visible()
pyglet.app.run()