Related
I am working on a project and I need to use texture arrays to apply textures. I have asked many questions about this, none of which I got an answer I was completely satisfied with (Get access to later versions of GLSL , OpenGL: Access Array Texture in GLSL , and OpenGL: How would I implement texture arrays?) so I'm asking a more broad question to hopefully get a response. Anyways, How would I texture an object in OpenGL (PyOpenGL more specifically, but it's fine if you put your answer in C++). I already have a way to load the texture arrays, just not a way to apply it. This is the desired result:
Image from opengl-tutorial
and this is what I currently have for loading array textures:
def load_texture_array(path,width,height):
teximg = pygame.image.load(path)
texels = teximg.get_buffer().raw
texture = GLuint(0)
layerCount = 6
mipLevelCount = 1
glGenTextures(1, texture)
glBindTexture(GL_TEXTURE_2D_ARRAY, texture)
glTexStorage3D(GL_TEXTURE_2D_ARRAY, mipLevelCount, GL_RGBA8, width, height, layerCount)
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, width, height, layerCount, GL_RGBA, GL_UNSIGNED_BYTE, texels)
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)
TLDR: How would I apply textures to objects in OpenGL using texture arrays?
I will happily provide any other information if necessary.
If you want to use a 2D Array Texture for a cube, each of the 6 textures for the 6 side must be the same size.
You can lookup the texture by 3 dimensional texture coordinates. The 3rd component of the texture coordinate is the index of the 2d texture in the 2d texture array.
Hence the texture coordinates for the 6 sides are
0: [(0, 0, 0), (1, 0, 0), (1, 1, 0), (0, 1, 0)]
1: [(0, 0, 1), (1, 0, 1), (1, 1, 1), (0, 1, 1)]
2: [(0, 0, 2), (1, 0, 2), (1, 1, 2), (0, 1, 2)]
3: [(0, 0, 3), (1, 0, 3), (1, 1, 3), (0, 1, 3)]
4: [(0, 0, 4), (1, 0, 4), (1, 1, 4), (0, 1, 4)]
5: [(0, 0, 5), (1, 0, 5), (1, 1, 5), (0, 1, 5)]
Get the 3 dimensional texture coordinate attribute in the vertex shader and pass it to the fragment shader:
in a_uv;
out v_uv;
// [...]
void main()
{
v_uv = a_uv;
// [...]
}
Use the 3 dimensional texture coordinate to look up the sampler2DArray in the fragment shader:
out v_uv;
uniform sampler2DArray u_texture;
// [...]
void main()
{
vec4 texture(u_texture, v_uv.xyz);
// [...]
}
Create a GL_TEXTURE_2D_ARRAY and use glTexSubImage3D to load 6 2-dimensional images to the 6 planes of the 2D Array Texture. In the following image_planes is a list with the 6 2-dimensional image planes:
tex_obj = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D_ARRAY, self.tex_obj)
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, sizeX, sizeY, 6, 0, GL_RGBA, GL_UNSIGNED_BYTE, None)
for i in range(6):
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, sizeX, sizeY, 1, GL_RGBA, GL_UNSIGNED_BYTE, image_planes[i])
glTexParameterf(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameterf(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
See also PyGame and OpenGL 4.
Minimal example:
import os, math, ctypes
import glm
from OpenGL.GL import *
from OpenGL.GL.shaders import *
from OpenGL.arrays import *
import pygame
pygame.init()
image_path = r"images"
image_names = ["banana64.png", "apple64.png", "fish64.png", "rocket64.png", "ice64.png", "boomerang64.png"]
image_planes = [
(GLubyte * 4)(255, 0, 0, 255), (GLubyte * 4)(0, 255, 0, 255), (GLubyte * 4)(0, 0, 255, 255),
(GLubyte * 4)(255, 255, 0, 255), (GLubyte * 4)(0, 255, 255, 255), (GLubyte * 4)(255, 0, 255, 255)]
image_size = (1, 1)
for i, filename in enumerate(image_names):
try:
image = pygame.image.load(os.path.join(image_path, filename))
image_size = image.get_size()
image_planes[i] = pygame.image.tostring(image, 'RGBA')
except:
pass
class MyWindow:
__glsl_vert = """
#version 130
in vec3 a_pos;
in vec3 a_nv;
in vec3 a_uv;
out vec3 v_pos;
out vec3 v_nv;
out vec3 v_uv;
uniform mat4 u_proj;
uniform mat4 u_view;
uniform mat4 u_model;
void main()
{
mat4 model_view = u_view * u_model;
mat3 normal = mat3(model_view);
vec4 view_pos = model_view * vec4(a_pos.xyz, 1.0);
v_pos = view_pos.xyz;
v_nv = normal * a_nv;
v_uv = a_uv;
gl_Position = u_proj * view_pos;
}
"""
__glsl_frag = """
#version 130
out vec4 frag_color;
in vec3 v_pos;
in vec3 v_nv;
in vec3 v_uv;
uniform sampler2DArray u_texture;
void main()
{
vec3 N = normalize(v_nv);
vec3 V = -normalize(v_pos);
float ka = 0.1;
float kd = max(0.0, dot(N, V)) * 0.9;
vec4 color = texture(u_texture, v_uv.xyz);
frag_color = vec4(color.rgb * (ka + kd), color.a);
}
"""
def __init__(self, w, h):
self.__caption = 'OpenGL Window'
self.__vp_size = [w, h]
pygame.display.gl_set_attribute(pygame.GL_DEPTH_SIZE, 24)
self.__screen = pygame.display.set_mode(self.__vp_size, pygame.DOUBLEBUF| pygame.OPENGL)
self.__program = compileProgram(
compileShader( self.__glsl_vert, GL_VERTEX_SHADER ),
compileShader( self.__glsl_frag, GL_FRAGMENT_SHADER ),
)
self.___attrib = { a : glGetAttribLocation (self.__program, a) for a in ['a_pos', 'a_nv', 'a_uv'] }
print(self.___attrib)
self.___uniform = { u : glGetUniformLocation (self.__program, u) for u in ['u_model', 'u_view', 'u_proj'] }
print(self.___uniform)
v = [[-1,-1,1], [1,-1,1], [1,1,1], [-1,1,1], [-1,-1,-1], [1,-1,-1], [1,1,-1], [-1,1,-1]]
n = [[0,0,1], [1,0,0], [0,0,-1], [-1,0,0], [0,1,0], [0,-1,0]]
e = [[0,1,2,3], [1,5,6,2], [5,4,7,6], [4,0,3,7], [3,2,6,7], [1,0,4,5]]
t = [[0, 0], [1, 0], [1, 1], [0, 1]]
index_array = [si*4+[0, 1, 2, 0, 2, 3][vi] for si in range(6) for vi in range(6)]
attr_array = []
for si in range(len(e)):
for i, vi in enumerate(e[si]):
attr_array += [*v[vi], *n[si], *t[i], si]
self.__no_vert = len(attr_array) // 10
self.__no_indices = len(index_array)
vertex_attributes = (ctypes.c_float * len(attr_array))(*attr_array)
indices = (ctypes.c_uint32 * self.__no_indices)(*index_array)
self.__vao = glGenVertexArrays(1)
self.__vbo, self.__ibo = glGenBuffers(2)
glBindVertexArray(self.__vao)
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, self.__ibo)
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices, GL_STATIC_DRAW)
glBindBuffer(GL_ARRAY_BUFFER, self.__vbo)
glBufferData(GL_ARRAY_BUFFER, vertex_attributes, GL_STATIC_DRAW)
float_size = ctypes.sizeof(ctypes.c_float)
glVertexAttribPointer(self.___attrib['a_pos'], 3, GL_FLOAT, False, 9*float_size, None)
glVertexAttribPointer(self.___attrib['a_nv'], 3, GL_FLOAT, False, 9*float_size, ctypes.c_void_p(3*float_size))
glVertexAttribPointer(self.___attrib['a_uv'], 3, GL_FLOAT, False, 9*float_size, ctypes.c_void_p(6*float_size))
glEnableVertexAttribArray(self.___attrib['a_pos'])
glEnableVertexAttribArray(self.___attrib['a_nv'])
glEnableVertexAttribArray(self.___attrib['a_uv'])
glEnable(GL_DEPTH_TEST)
glUseProgram(self.__program)
glActiveTexture(GL_TEXTURE0)
sizeX, sizeY = image_size
self.tex_obj = glGenTextures(1)
glBindTexture(GL_TEXTURE_2D_ARRAY, self.tex_obj)
glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA, sizeX, sizeY, 6, 0, GL_RGBA, GL_UNSIGNED_BYTE, None)
for i in range(6):
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, i, sizeX, sizeY, 1, GL_RGBA, GL_UNSIGNED_BYTE, image_planes[i])
glTexParameterf(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR)
glTexParameterf(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
def run(self):
self.__starttime = 0
self.__starttime = self.elapsed_ms()
run = True
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
self.__mainloop()
pygame.display.flip()
pygame.quit()
def elapsed_ms(self):
return pygame.time.get_ticks() - self.__starttime
def __mainloop(self):
proj, view, model = glm.mat4(1), glm.mat4(1), glm.mat4(1)
aspect = self.__vp_size[0]/self.__vp_size[1]
proj = glm.perspective(glm.radians(90.0), aspect, 0.1, 10.0)
view = glm.lookAt(glm.vec3(0,-3,0), glm.vec3(0, 0, 0), glm.vec3(0,0,1))
angle1 = self.elapsed_ms() * math.pi * 2 / 5000.0
angle2 = self.elapsed_ms() * math.pi * 2 / 7333.0
model = glm.rotate(model, angle1, glm.vec3(1, 0, 0))
model = glm.rotate(model, angle2, glm.vec3(0, 1, 0))
glUniformMatrix4fv(self.___uniform['u_proj'], 1, GL_FALSE, glm.value_ptr(proj) )
glUniformMatrix4fv(self.___uniform['u_view'], 1, GL_FALSE, glm.value_ptr(view) )
glUniformMatrix4fv(self.___uniform['u_model'], 1, GL_FALSE, glm.value_ptr(model) )
glClearColor(0.2, 0.3, 0.3, 1.0)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
glDrawElements(GL_TRIANGLES, self.__no_indices, GL_UNSIGNED_INT, None)
window = MyWindow(800, 600)
window.run()
Overview
We have an image/movie viewer powered via Qt5 and OpenGL that performs well however, we find that the draw surface itself consumes a large swath of resources no matter what is playing, even when we strip down the shaders.
We've added timing (gl query timers) for all of our own, custom, tools and cannot locate the source of the additional performance draw. The draw times are quite low considering our use case (~7ms per frame).
This is for an image rendering app; So 2D textures only. We use 2 triangles to cover the viewport and then dynamically generate the fragment based on the input requirements. This is for the film industry in which we do many many things to an image in terms of color and composite.
Discovery
We've stripped down the fragment to near nothing:
#version 330 core
void main() {
outColor = vec4(0.5, 0.0, 0.0, 1.0);
}
We've also disabled all PBO usage and have no uniform assignment. All timers read 0-1024ns for all of their commands (because all of our own gl commands are disabled)
The draw surface only calls paintGL, the Qt paint event for their OpenGL widget, once every ~42ms (24fps). Even with the simplicity of this, we use 70-80% of a GTX 1050 (resolution 3000x2000)
While this card is by no means a powerhouse, we are expecting to see less usage than that for something as simple as a solid color fragment. If we shrink the OpenGL window down to nothing, to only render the additional UI elements, we see ~5% usage. So our OpenGL surface that's doing next to nothing at a fixed frame rate is still consuming ~60-70% of the GPU for a reason we cannot determine.
Misc Info:
https://forum.qt.io/topic/121179/high-gpu-usage-when-animating/2
Additional Testing
We've attempted an apitrace for the gl commands but nothing jumped out at us. This included turn off the blit from our render frame buffer to the windows' buffer. So realistically, all of these are internal Qt-driven OpenGL commands.
10008 glViewport(x = 0, y = 0, width = 3000, height = 1896)
10009 glClearColor(red = 0, green = 0, blue = 0, alpha = 1)
10010 glClear(mask = GL_COLOR_BUFFER_BIT)
10011 glBindVertexArray(array = 1)
10012 glUseProgram(program = 7)
10013 glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 9)
10014 glVertexAttribPointer(index = 0, size = 3, type = GL_FLOAT, normalized = GL_TRUE, stride = 0, pointer = NULL)
10015 glEnableVertexAttribArray(index = 0)
10016 glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 0)
10017 glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 10)
10018 glVertexAttribPointer(index = 1, size = 2, type = GL_FLOAT, normalized = GL_TRUE, stride = 0, pointer = NULL)
10019 glEnableVertexAttribArray(index = 1)
10020 glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 0)
10021 glBindTexture(target = GL_TEXTURE_2D, texture = 1)
10022 glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 9)
10023 glVertexAttribPointer(index = 0, size = 3, type = GL_FLOAT, normalized = GL_TRUE, stride = 0, pointer = NULL)
10024 glEnableVertexAttribArray(index = 0)
10025 glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 0)
10026 glUniformMatrix4fv(location = 4, count = 1, transpose = GL_FALSE, value = {1, 0, 0, 0, 0, 0.8016878, 0, 0, 0, 0, 1, 0, 0, 0.1476793, 0, 1})
10027 glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 10)
10028 glVertexAttribPointer(index = 1, size = 2, type = GL_FLOAT, normalized = GL_TRUE, stride = 0, pointer = NULL)
10029 glEnableVertexAttribArray(index = 1)
10030 glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 0)
10031 glUniform1i(location = 1, v0 = 0)
10032 glUniformMatrix3fv(location = 3, count = 1, transpose = GL_FALSE, value = {1, 0, 0, 0, 1, 0, 0, 0, 1})
10033 glDrawArrays(mode = GL_TRIANGLES, first = 0, count = 6)
10034 glBindTexture(target = GL_TEXTURE_2D, texture = 0)
10035 glPixelStorei(pname = GL_UNPACK_ROW_LENGTH, param = 3000)
10036 glBindTexture(target = GL_TEXTURE_2D, texture = 2)
10037 glTexSubImage2D(target = GL_TEXTURE_2D, level = 0, xoffset = 0, yoffset = 48, width = 3000, height = 1520, format = GL_RGBA, type = GL_UNSIGNED_BYTE, pixels = blob(18240000))
10038 glPixelStorei(pname = GL_UNPACK_ROW_LENGTH, param = 0)
10039 glEnable(cap = GL_BLEND)
10040 glBlendFuncSeparate(sfactorRGB = GL_ONE, dfactorRGB = GL_ONE_MINUS_SRC_ALPHA, sfactorAlpha = GL_ONE, dfactorAlpha = GL_ONE)
10041 glBindTexture(target = GL_TEXTURE_2D, texture = 2)
10042 glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 9)
10043 glVertexAttribPointer(index = 0, size = 3, type = GL_FLOAT, normalized = GL_TRUE, stride = 0, pointer = NULL)
10044 glEnableVertexAttribArray(index = 0)
10045 glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 0)
10046 glUniformMatrix4fv(location = 4, count = 1, transpose = GL_FALSE, value = {1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1})
10047 glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 10)
10048 glVertexAttribPointer(index = 1, size = 2, type = GL_FLOAT, normalized = GL_TRUE, stride = 0, pointer = NULL)
10049 glEnableVertexAttribArray(index = 1)
10050 glBindBuffer(target = GL_ARRAY_BUFFER, buffer = 0)
10051 glUniform1i(location = 1, v0 = 1)
10052 glUniformMatrix3fv(location = 3, count = 1, transpose = GL_FALSE, value = {1, 0, 0, 0, -1, 0, 0, 1, 1})
10053 glDrawArrays(mode = GL_TRIANGLES, first = 0, count = 6)
10054 glBindTexture(target = GL_TEXTURE_2D, texture = 0)
10055 glDisable(cap = GL_BLEND)
10056 glUseProgram(program = 0)
10057 glBindVertexArray(array = 0)
10058 wglSwapBuffers(hdc = 0xffffffff86011399) = TRUE
Any ideas would be excellent. I'm also happy to provide more information if required.
I performed an MVP transformation on the vertices of the model. In theory, I must apply the inverse transpose matrix of the MVP transformation to the normal.
This is the derivation process:
(A, B, C) is the normal of the plane where the point (x, y, z) lies
For a vector, such as (x0, y0, z0), it is (x0, y0, z0, 0) in homogeneous coordinates. After transformation, it should still be a vector, like (x1, y1, z1, 0), This requires that the last row of the 4 * 4 transformation matrix is all 0 except for the elements in the last column, otherwise it will become (x1, y1, z1, n) after the transformation.
In fact, my MVP transformation matrix cannot satisfy this point after undergoing inverse transpose transformation.
Code:
Mat<4, 4> View(const Vec3& pos){
Mat<4, 4> pan{1, 0, 0, -pos.x,
0, 1, 0, -pos.y,
0, 0, 1, -pos.z,
0, 0, 0, 1};
Vec3 v = Cross(camera.lookAt, camera.upDirection).Normalize();
Mat<4, 4> rotate{v.x, v.y, v.z, 0,
camera.upDirection.x, camera.upDirection.y, camera.upDirection.z, 0,
-camera.lookAt.x, -camera.lookAt.y, -camera.lookAt.z, 0,
0, 0, 0, 1};
return rotate * pan;
}
Mat<4, 4> Projection(double near, double far, double fov, double aspectRatio){
double angle = fov * PI / 180;
double t = -near * tan(angle / 2);
double b = -t;
double r = t * aspectRatio;
double l = -r;
Mat<4, 4> zoom{2 / (r - l), 0, 0, 0,
0, 2 / (t - b), 0, 0,
0, 0, 2 / (near - far), 0,
0, 0, 0, 1};
Mat<4, 4> pan{1, 0, 0, -(l + r) / 2,
0, 1, 0, -(t + b) / 2,
0, 0, 1, -(near + far) / 2,
0, 0, 0, 1};
Mat<4, 4> extrusion{near, 0, 0, 0,
0, near, 0, 0,
0, 0, near + far, -near * far,
0, 0, 1, 0};
Mat<4, 4> ret = zoom * pan * extrusion;
return ret;
}
Mat<4, 4> modelMatrix = Mat<4, 4>::identity();
Mat<4, 4> viewMatrix = View(camera.position);
Mat<4, 4> projectionMatrix = Projection(-0.1, -50, camera.fov, camera.aspectRatio);
Mat<4, 4> mvp = projectionMatrix * viewMatrix * modelMatrix;
Mat<4, 4> mvpInverseTranspose = mvp.Inverse().Transpose();
mvp:
-2.29032 0 0.763441 -2.68032e-16
0 -2.41421 0 0
-0.317495 0 -0.952486 2.97455
0.316228 0 0.948683 -3.16228
mvpInverseTranspose:
-0.392957 0 0.130986 0
0 -0.414214 0 0
-4.99 0 -14.97 -4.99
-4.69377 0 -14.0813 -5.01
I seem to understand the problem. The lighting should be calculated in world space, so I only need to apply the inverse transpose matrix of the model transformation to the normal.
I'm trying to take a noise pattern which consists of black and white (and grey where there is a smooth transition between the two) and I am trying to map it to two different colours, but I'm having trouble figuring out how to do this.
I can easily replace the white or black with a simple if statement, but the gradient areas where the white and black is mixed is still a mix of white and black, which makes sense. So I need to actually the map the colours to the new colours, but I have no idea the way I'm supposed to go about this.
There are easy ways
The inflexible way, use mix
gl_FragColor = mix(color0, color1, noise)
The more flexible way, use a ramp texture
float u = (noise * (rampTextureWidth - 1.0) + 0.5) / rampTextureWidth;
gl_FragColor = texture2D(rampTexture, vec2(u, 0.5));
Using ramp textures handles any number of colors where as mix only handles 2.
const vs = `
attribute vec4 position;
attribute float noise;
uniform mat4 u_matrix;
varying float v_noise;
void main() {
gl_Position = u_matrix * position;
v_noise = noise;
}
`;
const fs = `
precision highp float;
varying float v_noise;
uniform sampler2D rampTexture;
uniform float rampTextureWidth;
void main() {
float u = (v_noise * (rampTextureWidth - 1.0) + 0.5) / rampTextureWidth;
gl_FragColor = texture2D(rampTexture, vec2(u, 0.5));
}
`;
"use strict";
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
// compiles shaders, links program, looks up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
/*
6------7
/| /|
/ | / |
2------3 |
| | | |
| 4---|--5
| / | /
|/ |/
0------1
*/
const arrays = {
position: [
-1, -1, -1,
1, -1, -1,
-1, 1, -1,
1, 1, -1,
-1, -1, 1,
1, -1, 1,
-1, 1, 1,
1, 1, 1,
],
noise: {
numComponents: 1,
data: [
1, 0.5, 0.2, 0.3, 0.9, 0.1, 0.7, 1,
],
},
indices: [
0, 2, 1, 1, 2, 3,
1, 3, 5, 5, 3, 7,
5, 7, 4, 4, 7, 6,
4, 6, 0, 0, 6, 2,
2, 6, 3, 6, 7, 3,
0, 1, 4, 4, 1, 5,
],
};
const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);
const red = [255, 0, 0, 255];
const yellow = [255, 255, 0, 255];
const blue = [ 0, 0, 255, 255];
const green = [ 0, 255, 0, 255];
const cyan = [ 0, 255, 255, 255];
const magenta = [255, 0, 255, 255];
function makeTexture(gl, name, colors) {
const width = colors.length / 4;
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA,
width, 1, 0,
gl.RGBA, gl.UNSIGNED_BYTE,
new Uint8Array(colors));
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
return {
name,
texture,
width,
};
}
const textures = [
makeTexture(gl, 'one color',
[...red]),
makeTexture(gl, 'two colors',
[...red, ...yellow]),
makeTexture(gl, 'three colors',
[...blue, ...red, ...yellow]),
makeTexture(gl, 'six colors',
[...green, ...red, ...blue, ...yellow, ...cyan, ...magenta]),
];
const infoElem = document.querySelector('#info');
function render(time) {
time *= 0.001;
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.enable(gl.DEPTH_TEST);
gl.enable(gl.CULL_FACE);
// draw cube
const fov = 30 * Math.PI / 180;
const aspect = gl.canvas.clientWidth / gl.canvas.clientHeight;
const zNear = 0.5;
const zFar = 40;
const projection = m4.perspective(fov, aspect, zNear, zFar);
const eye = [1, 4, -7];
const target = [0, 0, 0];
const up = [0, 1, 0];
const camera = m4.lookAt(eye, target, up);
const view = m4.inverse(camera);
const viewProjection = m4.multiply(projection, view);
const world = m4.rotationY(time);
gl.useProgram(programInfo.program);
const tex = textures[time / 2 % textures.length | 0];
infoElem.textContent = tex.name;
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
// calls gl.uniformXXX, gl.activeTexture, gl.bindTexture
twgl.setUniformsAndBindTextures(programInfo, {
u_matrix: m4.multiply(viewProjection, world),
rampTexture: tex.texture,
rampTextureWidth: tex.width,
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
<div id="info"></div>
I am attempting to write a WebGL image filter using the PixiJS library. My filter should take an array of arrays, where each inner array represents a possible pixel color. The function will then decide which element in the outer array to use.
I have managed to write a simple GLSL function which takes a single color array as a "uniform" argument, however I can't figure out how to pass the nested array. Could you point me to the proper type declaration to accept a nested array of floats from this snippet?
var fragmentSrc = [
"uniform vec4 colorList;", // WHAT TYPE DO I NEED HERE TO PASS THE ARRY IN THE COMMENT BELOW?
"void main() {",
" float GrayScale = (gl_FragCoord.r * 299.0 / 1000.0) + (gl_FragCoord.g * 587.0 / 1000.0) + (gl_FragCoord.b * 114.0 / 1000.0);",
" float sigmoidThreshold = 1.0 / (1.0 + pow(2.7182818284590452353602874713527, (-((GrayScale - 128.0) /32.0))));",
" gl_FragColor = colorList;",
"}",
];
var renderer = PIXI.autoDetectRenderer(750, 750);
document.body.appendChild(renderer.view);
var stage = new PIXI.Container();
function CustomFilter(fragmentSource) {
PIXI.Filter.call(this,
null,
fragmentSource
);
}
CustomFilter.prototype = Object.create(PIXI.Filter.prototype);
CustomFilter.prototype.constructor = CustomFilter;
var bg = new PIXI.Graphics();
bg.drawRect(0, 0, 375, 375);
bg.endFill();
stage.addChild(bg);
var filter = new CustomFilter(fragmentSrc.join('\r\n'));
filter.uniforms.colorList = [1.0, 1.0, 0.0, 1.0] // WANT TO PASS AN ARRAY OF ARRAYS LIKE:
// [[1.0, 1.0, 0.0, 1.0], [0.0, 0.0, 1.0, 1.0]]
bg.filters = [filter];
renderer.render(stage);
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.5.1/pixi.min.js"></script>
var fragmentSrc = [
"uniform vec4 colorList;", // WHAT TYPE DO I NEED HERE TO PASS THE ARRY IN THE COMMENT BELOW?
"void main() {",
" float GrayScale = (gl_FragCoord.r * 299.0 / 1000.0) + (gl_FragCoord.g * 587.0 / 1000.0) + (gl_FragCoord.b * 114.0 / 1000.0);",
" float sigmoidThreshold = 1.0 / (1.0 + pow(2.7182818284590452353602874713527, (-((GrayScale - 128.0) /32.0))));",
" gl_FragColor = colorList;",
"}",
];
I changed the code to this
var fragmentSrc = `
uniform vec4 colorList[10];
void main() {
float GrayScale = (gl_FragCoord.r * 299.0 / 1000.0) + (gl_FragCoord.g * 587.0 / 1000.0) + (gl_FragCoord.b * 114.0 / 1000.0);
float sigmoidThreshold = 1.0 / (1.0 + pow(2.7182818284590452353602874713527, (-((GrayScale - 128.0) /32.0))));
gl_FragColor = colorList[9];
}
`;
var filter = new CustomFilter(fragmentSrc);
console.log(filter.uniforms);
And it prints
so this works
filter.uniforms.colorList = [
1, 0, 0, 0, // 0
1, 1, 0, 0, // 1
0, 1, 0, 0, // 2
0, 1, 1, 0, // 3
0, 0, 1, 0, // 4
1, 0, 1, 0, // 5
.5, 0, 0, 0, // 6
0, .5, 0, 0, // 7
1, 1, 0, 1, // 8
.5, .5, .7, 1., // 9
];
and this
filter.uniforms.colorList = new Float32Array([
1, 0, 0, 0, // 0
1, 1, 0, 0, // 1
0, 1, 0, 0, // 2
0, 1, 1, 0, // 3
0, 0, 1, 0, // 4
1, 0, 1, 0, // 5
.5, 0, 0, 0, // 6
0, .5, 0, 0, // 7
1, 1, 0, 1, // 8
.5, .5, .7, 1., // 9
]);
etc...
If you really want an array of arrays in JavaScript you can do this to make ArrayBufferViews into the larger array
const colorValues = [];
for (let i = 0; i < filter.uniforms.colorList.length; i += 4) {
const buffer = filter.uniforms.colorList.buffer;
const byteOffset = i * Float32Array.BYTES_PER_ELEMENT;
const length = 4;
colorValues.push(new Float32Array(buffer, byteOffset, length));
}
now you can set an array element like this
colorValues[9].set([1, 1, 0, 1]);
var fragmentSrc = `
uniform vec4 colorList[10]; // WHAT TYPE DO I NEED HERE TO PASS THE ARRY IN THE COMMENT BELOW?
void main() {
float GrayScale = (gl_FragCoord.r * 299.0 / 1000.0) + (gl_FragCoord.g * 587.0 / 1000.0) + (gl_FragCoord.b * 114.0 / 1000.0);
float sigmoidThreshold = 1.0 / (1.0 + pow(2.7182818284590452353602874713527, (-((GrayScale - 128.0) /32.0))));
gl_FragColor = colorList[9];
}
`;
var renderer = PIXI.autoDetectRenderer(750, 750);
document.body.appendChild(renderer.view);
var stage = new PIXI.Container();
function CustomFilter(fragmentSource) {
PIXI.Filter.call(this,
null,
fragmentSource
);
}
CustomFilter.prototype = Object.create(PIXI.Filter.prototype);
CustomFilter.prototype.constructor = CustomFilter;
var bg = new PIXI.Graphics();
bg.drawRect(0, 0, 375, 375);
bg.endFill();
stage.addChild(bg);
var filter = new CustomFilter(fragmentSrc);
const colorValues = [];
for (let i = 0; i < filter.uniforms.colorList.length; i += 4) {
const buffer = filter.uniforms.colorList.buffer;
const byteOffset = i * Float32Array.BYTES_PER_ELEMENT;
const length = 4;
colorValues.push(new Float32Array(buffer, byteOffset, length));
}
colorValues[9].set([1, 1, 0, 1]);
bg.filters = [filter];
renderer.render(stage);
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.5.1/pixi.min.js"></script>