With WebGL 2 we now can play with Uniform Buffer Objects.
They look like a great idea, not having to attach common uniforms to every single program (like projection and view matrices that are common to every object being rendered).
I created an helper class which I call every time I want to bind a uniform buffer object.
class UniformBuffer {
constructor(gl, data, boundLocation = 0) {
this.boundLocation = boundLocation;
this.data = new Float32Array(data);
this.buffer = gl.createBuffer();
gl.bindBuffer(gl.UNIFORM_BUFFER, this.buffer);
gl.bufferData(gl.UNIFORM_BUFFER, this.data, gl.DYNAMIC_DRAW);
gl.bindBuffer(gl.UNIFORM_BUFFER, null);
gl.bindBufferBase(gl.UNIFORM_BUFFER, this.boundLocation, this.buffer);
}
update(gl, data, offset = 0) {
this.data.set(data, offset);
gl.bindBuffer(gl.UNIFORM_BUFFER, this.buffer);
gl.bufferSubData(gl.UNIFORM_BUFFER, 0, this.data, 0, null);
gl.bindBuffer(gl.UNIFORM_BUFFER, null);
gl.bindBufferBase(gl.UNIFORM_BUFFER, this.boundLocation, this.buffer);
}
};
The idea if to create the uniform buffers like this
const perScene = new UniformBuffer(gl, [
...vec4.create(),
...vec4.create(),
], 0); // and bind it to bind location 0?
const perObject = new UniformBuffer(gl, [
...vec4.create(),
], 1); // and bind it to bind location 1?
In my render loop, I then update the "perScene" uniforms by calling
perScene.update(gl, [
...vec4.fromValues(1, 0, 0, 1),
], 4); // giving an offset to update only the 2nd color.
Then I'll look through all the objects in the scene and my idea is to update the perObject uniform buffer like this
for (let i = 0; i < objects.length; i++) {
perObject.update(gl, [
...vec4.fromValues(0, 0, 1, 1),
]);
}
I'm talking about vec4 just to make the example easier, but the idea is to have matrices (projection and view) on the perScene, and (object and normal matrices) on the perObject.
In my shader I have them declared as
uniform perScene {
vec4 color1;
vec4 color2;
};
uniform perModel {
vec4 color3;
};
I have a working snippet here
class UniformBuffer {
constructor(gl, data, boundLocation = 0) {
this.boundLocation = boundLocation;
this.data = new Float32Array(data);
this.buffer = gl.createBuffer();
gl.bindBuffer(gl.UNIFORM_BUFFER, this.buffer);
gl.bufferData(gl.UNIFORM_BUFFER, this.data, gl.DYNAMIC_DRAW);
gl.bindBuffer(gl.UNIFORM_BUFFER, null);
gl.bindBufferBase(gl.UNIFORM_BUFFER, this.boundLocation, this.buffer);
}
update(gl, data, offset = 0) {
this.data.set(data, offset);
gl.bindBuffer(gl.UNIFORM_BUFFER, this.buffer);
gl.bufferSubData(gl.UNIFORM_BUFFER, 0, this.data, 0, null);
gl.bindBuffer(gl.UNIFORM_BUFFER, null);
gl.bindBufferBase(gl.UNIFORM_BUFFER, this.boundLocation, this.buffer);
}
};
const vertex = `#version 300 es
uniform perScene {
vec4 color1;
vec4 color2;
};
uniform perModel {
vec4 color3;
};
in vec3 a_position;
out vec3 v_color;
void main() {
gl_Position = vec4(a_position, 1.0);
v_color = color1.rgb + color2.rgb; // WORKS
// v_color = color1.rgb + color2.rgb + color3.rgb; // DOESNT WORK
}
`;
const fragment = `#version 300 es
precision highp float;
precision highp int;
in vec3 v_color;
out vec4 outColor;
void main() {
outColor = vec4(v_color, 1.0);
}
`;
const geometry = {
positions: [-0.5, -0.5, 0, -0.5, 0.5, 0, 0.5, -0.5, 0, 0.5, 0.5, 0],
indices: [0, 2, 1, 1, 2, 3],
};
const renderList = [];
// STEP 1 (create canvas)
var canvas = document.getElementById("canvas");
var gl = canvas.getContext("webgl2");
if (!gl) {
console.log('no webgl2 buddy');
}
// STEP 2 (create program)
const v = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(v, vertex);
gl.compileShader(v);
const f = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(f, fragment);
gl.compileShader(f);
const program = gl.createProgram();
gl.attachShader(program, v);
gl.attachShader(program, f);
gl.linkProgram(program);
// STEP 3 (create VAO)
const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
const colorUniformLocation = gl.getUniformLocation(program, 'color');
const positionsBuffer = gl.createBuffer();
const indicesBuffer = gl.createBuffer();
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
// position & indices
gl.enableVertexAttribArray(positionAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, positionsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(geometry.positions), gl.STATIC_DRAW);
gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indicesBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(geometry.indices), gl.STATIC_DRAW);
// STEP 4 (create UBO)
// bound to location 0
const perScene = new UniformBuffer(gl, [
...vec4.create(), // color 1
...vec4.create(), // color 2
], 0);
// bound to location 1 ?
const perModel = new UniformBuffer(gl, [
...vec4.create(), // color 3
], 3);
// STEP 5 (add instances)
for (let i = 0; i < 1; i++) {
renderList.push({
id: i,
vao: vao,
program: program,
color: [0, 1, 1],
});
}
// STEP 6 (draw)
gl.clearColor(0, 0, 0, 0);
gl.enable(gl.DEPTH_TEST);
gl.viewport(0, 0, canvas.width, canvas.height);
perScene.update(gl, [
...vec4.fromValues(1, 0, 0, 1),
...vec4.fromValues(0, 1, 0, 1),
]);
for (let i = 0; i < renderList.length; i++) {
const current = renderList[i];
gl.useProgram(current.program);
gl.bindVertexArray(current.vao);
// update perObject
perModel.update(gl, [
...vec4.fromValues(0, 0, 1, 1),
]);
gl.drawElements(gl.TRIANGLES, geometry.indices.length, gl.UNSIGNED_SHORT, 0);
// unbind
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
console.log('compiled!');
canvas {
background-color: black;
}
<canvas id="canvas"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.3.2/gl-matrix-min.js"></script>
Shouldn't I be seeing a white square since all colours added up result in a vec4(1.0, 1.0, 1.0, 1.0)? (jsfiddle line 41)
What am I doing wrong?
Thanks
So, the first thing you're doing wrong is you're not calling gl.getUniformBlockIndex. Just like uniform you have to query the location or in this case the index of each block.
The second thing is block uniforms are indirected one level and you need to call gl.uniformBlockBinding(program, uniformBlockIndex, uniformBufferIndex);
uniformBlockIndex is the index you got from gl.getUniformBlockIndex. uniformBufferIndex similar to a texture unit. There are N uniform buffer indices. You can choose any buffer index from 0 to MAX_UNIFORM_BUFFER_BINDINGS - 1.
This indirection helps if you have one program that uses blocks A, B and another that uses A and C. In this case block A might have a different index in the 2 programs but you have it pull its values from the same uniformBufferIndex.
Note that this state is per program state so can probably set it at init time if you plan to always use the same uniform buffer index for the same uniform block.
To spell it out even more. You have a shader program. It has state
var someProgram = {
uniforms: {
projectionMatrix: [1, 0, 0, 0, 0, ... ], // etc
},
uniformBlockIndcies[ // one per uniform block
0,
0,
0,
],
...
}
Next you have uniform buffer indices which are global state
glState = {
textureUnits: [ ... ],
uniformBuffers: [ null, null, null ..., ],
};
You tell the program for each uniform buffer block, which uniform buffer index to use with gl.uniformBlockBinding. You then bind a buffer to that index with gl.bindBufferBase or gl.bindBufferRange.
It's very similar to telling a program which texture unit to use and then binding a texture to that unit. When you do this, at init time or render time is really up to you. In my mind it seems more likely I could decide at init time that my perScene stuff is always on buffer index 0 and perModel stuff at index 1 and therefore I could set them up the program parts (the calls to gl.uniformBlockBinding) at init time.
class UniformBuffer {
constructor(gl, data, boundLocation = 0) {
this.boundLocation = boundLocation;
this.data = new Float32Array(data);
this.buffer = gl.createBuffer();
gl.bindBuffer(gl.UNIFORM_BUFFER, this.buffer);
gl.bufferData(gl.UNIFORM_BUFFER, this.data, gl.DYNAMIC_DRAW);
gl.bindBuffer(gl.UNIFORM_BUFFER, null);
gl.bindBufferBase(gl.UNIFORM_BUFFER, this.boundLocation, this.buffer);
}
update(gl, data, offset = 0) {
this.data.set(data, offset);
gl.bindBuffer(gl.UNIFORM_BUFFER, this.buffer);
gl.bufferSubData(gl.UNIFORM_BUFFER, 0, this.data, 0, null);
gl.bindBuffer(gl.UNIFORM_BUFFER, null);
gl.bindBufferBase(gl.UNIFORM_BUFFER, this.boundLocation, this.buffer);
}
};
const vertex = `#version 300 es
uniform perScene {
vec4 color1;
vec4 color2;
};
uniform perModel {
vec4 color3;
};
in vec3 a_position;
out vec3 v_color;
void main() {
gl_Position = vec4(a_position, 1.0);
v_color = color1.rgb + color2.rgb + color3.rgb;
}
`;
const fragment = `#version 300 es
precision highp float;
precision highp int;
in vec3 v_color;
out vec4 outColor;
void main() {
outColor = vec4(v_color, 1.0);
}
`;
const geometry = {
positions: [-0.5, -0.5, 0, -0.5, 0.5, 0, 0.5, -0.5, 0, 0.5, 0.5, 0],
indices: [0, 2, 1, 1, 2, 3],
};
const renderList = [];
// STEP 1 (create canvas)
var canvas = document.getElementById("canvas");
var gl = canvas.getContext("webgl2");
if (!gl) {
console.log('no webgl2 buddy');
}
// STEP 2 (create program)
const v = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(v, vertex);
gl.compileShader(v);
const f = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(f, fragment);
gl.compileShader(f);
const program = gl.createProgram();
gl.attachShader(program, v);
gl.attachShader(program, f);
gl.linkProgram(program);
// STEP 3 (create VAO)
const positionAttributeLocation = gl.getAttribLocation(program, 'a_position');
const colorUniformLocation = gl.getUniformLocation(program, 'color');
const positionsBuffer = gl.createBuffer();
const indicesBuffer = gl.createBuffer();
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
// position & indices
gl.enableVertexAttribArray(positionAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, positionsBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(geometry.positions), gl.STATIC_DRAW);
gl.vertexAttribPointer(positionAttributeLocation, 3, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indicesBuffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(geometry.indices), gl.STATIC_DRAW);
// STEP 4 (create UBO)
// bound to location 0
const perScene = new UniformBuffer(gl, [
...vec4.create(), // color 1
...vec4.create(), // color 2
], 0);
// bound to location 1 ?
const perModel = new UniformBuffer(gl, [
...vec4.create(), // color 3
], 1);
gl.uniformBlockBinding(program, gl.getUniformBlockIndex(program, "perScene"), perScene.boundLocation);
gl.uniformBlockBinding(program, gl.getUniformBlockIndex(program, "perModel"), perModel.boundLocation);
// STEP 5 (add instances)
for (let i = 0; i < 1; i++) {
renderList.push({
id: i,
vao: vao,
program: program,
color: [0, 1, 1],
});
}
// STEP 6 (draw)
gl.clearColor(0, 0, 0, 0);
gl.enable(gl.DEPTH_TEST);
gl.viewport(0, 0, canvas.width, canvas.height);
perScene.update(gl, [
...vec4.fromValues(1, 0, 0, 1),
...vec4.fromValues(0, 1, 0, 1),
]);
for (let i = 0; i < renderList.length; i++) {
const current = renderList[i];
gl.useProgram(current.program);
gl.bindVertexArray(current.vao);
// update perObject
perModel.update(gl, [
...vec4.fromValues(0, 0, 1, 1),
]);
gl.drawElements(gl.TRIANGLES, geometry.indices.length, gl.UNSIGNED_SHORT, 0);
// unbind
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
}
console.log('compiled!');
canvas {
background-color: black;
}
<canvas id="canvas"></canvas>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gl-matrix/2.3.2/gl-matrix-min.js"></script>
In this example there are 5 uniform blocks.
the shared matrices like projection and view and viewProjection
the per model matrices like world and worldInverseTransform
the per light info like lightPosition and lightColor.
There are 2 lights so the 4th block is similar to the 3rd
the material data like ambient color, specularity, etc..
I'm not saying that's the perfect setup. I really have no idea. But it's pretty common to make something called a "material" and share that material among more than one model so that's like a perMaterial block which is different from a perModel block. It's also common to share lighting info. I don't know what the ideal setup is, just pointing out that perScene and perModel might not be enough for fairly common situations.
One other thing, this line
// unbind
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
makes no sense. ELEMENT_ARRAY_BUFFER is part of the VAO state.
As gman said, get the index of the uniform block and then bind it with the gl.bindBufferBase
Your updated class should look something like:
class UniformBuffer {
constructor(gl, data, program, uniformName, targetIndex = 0) {
this.data = new Float32Array(data);
const boundLocation = gl.getUniformBlockIndex(program, uniformName);
this.buffer = gl.createBuffer();
gl.bindBufferBase(gl.UNIFORM_BUFFER, boundLocation, this.buffer);
gl.bindBuffer(gl.UNIFORM_BUFFER, this.buffer);
gl.bufferData(gl.UNIFORM_BUFFER, this.data, gl.DYNAMIC_DRAW);
gl.bindBuffer(gl.UNIFORM_BUFFER, null);
}
update(gl, data, offset = 0) {
this.data.set(data, offset);
gl.bindBuffer(gl.UNIFORM_BUFFER, this.buffer);
gl.bufferSubData(gl.UNIFORM_BUFFER, 0, this.data, 0, null);
gl.bindBuffer(gl.UNIFORM_BUFFER, null);
}
};
Related
I have a problem with my tesellation shader. It renders when i don't use Hull Shader or Domain Shader. Just plain vertex and pixel shader works fine. Here is my VS and PS shaders:
VOut VShader(float4 position : POSITION, float4 color : COLOR)
{
VOut output;
output.position = mul(world, position);
output.color = color;
return output;
}
float4 PShader(float4 position : SV_POSITION, float4 color : COLOR) : SV_TARGET
{
return color;
}
I am using Orthographic Projection to map out pixels to their original positions. Here is the problematic shader. Which inputs float3 vertices and float4 colors.
cbuffer cbPerFrame : register(b0) {
matrix world; };
struct VS_CONTROL_POINT_INPUT {
float3 vPosition : POSITION; };
struct VS_CONTROL_POINT_OUTPUT {
float3 vPosition : POSITION; };
struct HS_CONSTANT_DATA_OUTPUT {
float Edges[3] : SV_TessFactor;
float Inside : SV_InsideTessFactor; };
struct HS_OUTPUT {
float3 vPosition : POSITION; };
HS_CONSTANT_DATA_OUTPUT ConstantHS(InputPatch<VS_CONTROL_POINT_OUTPUT, 3> ip,uint PatchID : SV_PrimitiveID) {
HS_CONSTANT_DATA_OUTPUT Output;
Output.Edges[0] = Output.Edges[1] = Output.Edges[2] = 4;
Output.Inside = 4;
return Output; }
[domain("tri")]
[partitioning("integer")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(3)]
[patchconstantfunc("ConstantHS")]
HS_OUTPUT HShader(InputPatch<VS_CONTROL_POINT_OUTPUT, 3> p, uint i : SV_OutputControlPointID, uint PatchID : SV_PrimitiveID)
{
HS_OUTPUT Output;
Output.vPosition = p[i].vPosition;
return Output;
}
struct DS_OUTPUT {
float4 vPosition : SV_POSITION; };
[domain("tri")]
DS_OUTPUT DShader(HS_CONSTANT_DATA_OUTPUT input, float3 UVW : SV_DomainLocation, const OutputPatch<HS_OUTPUT, 3> quad) {
DS_OUTPUT Output;
float3 finalPos = UVW.x * quad[0].vPosition + UVW.y * quad[1].vPosition + UVW.z * quad[2].vPosition;
Output.vPosition = mul(world,float4(finalPos, 1));
return Output; }
VS_CONTROL_POINT_OUTPUT VShader(VS_CONTROL_POINT_INPUT Input) {
VS_CONTROL_POINT_OUTPUT Output;
Output.vPosition = Input.vPosition;
return Output; }
float4 PShader(DS_OUTPUT Input) : SV_TARGET {
return float4(1, 0, 0, 1); }
My shader init. code:
D3DCompileFromFile(L"shader.hlsl", NULL, NULL, "VShader", "vs_5_0", 0, 0, &VS, &ERR);
D3DCompileFromFile(L"shader.hlsl", NULL, NULL, "PShader", "ps_5_0", 0, 0, &PS, &ERR);
D3DCompileFromFile(L"shader.hlsl", NULL, NULL, "HShader", "hs_5_0", 0, 0, &HS, &ERR);
D3DCompileFromFile(L"shader.hlsl", NULL, NULL, "DShader", "ds_5_0", 0, 0, &DS, &ERR);
dev->CreateVertexShader(VS->GetBufferPointer(), VS->GetBufferSize(), NULL, &pVS);
dev->CreatePixelShader(PS->GetBufferPointer(), PS->GetBufferSize(), NULL, &pPS);
dev->CreateHullShader(HS->GetBufferPointer(), HS->GetBufferSize(), NULL, &pHS);
dev->CreateDomainShader(DS->GetBufferPointer(), DS->GetBufferSize(), NULL, &pDS);
devcon->VSSetShader(pVS, 0, 0);
devcon->HSSetShader(pHS, 0, 0);
devcon->DSSetShader(pDS, 0, 0);
devcon->PSSetShader(pPS, 0, 0);
Input descriptor:
D3D11_INPUT_ELEMENT_DESC ied[] =
{
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0},
};
dev->CreateInputLayout(ied, 2, VS->GetBufferPointer(), VS->GetBufferSize(), &pLayout);
devcon->IASetInputLayout(pLayout);
Rasterizer:
D3D11_RASTERIZER_DESC RasterDesc = {};
RasterDesc.FillMode = D3D11_FILL_SOLID;
RasterDesc.CullMode = D3D11_CULL_NONE;
RasterDesc.DepthClipEnable = TRUE;
ID3D11RasterizerState* WireFrame=NULL;
dev->CreateRasterizerState(&RasterDesc, &WireFrame);
devcon->RSSetState(WireFrame);
Input Vertices:
OurVertices = (VERTEX*)malloc(PointCount * sizeof(VERTEX));
for (int i = 0; i < PointCount; i++)
{
OurVertices[i] = { RandOm() * i,RandOm() * i ,RandOm() ,{abs(RandOm()),abs(RandOm()),abs(RandOm()),1.0f} };
}
CBuffer:
ID3D11Buffer* g_pConstantBuffer11 = NULL;
cbuff.world = XMMatrixOrthographicOffCenterLH(SceneY - (ViewPortWidth / 2) * SceneZoom, SceneY + (ViewPortWidth / 2) * SceneZoom,
SceneX - (ViewPortHeight / 2) * SceneZoom, SceneX + (ViewPortHeight / 2) * SceneZoom,-10000.0f, 10000.0f);
D3D11_BUFFER_DESC cbDesc;
cbDesc.ByteWidth = sizeof(CBUFFER);
cbDesc.Usage = D3D11_USAGE_DYNAMIC;
cbDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
cbDesc.CPUAccessFlags = D3D11_CPU_ACCESS_WRITE;
cbDesc.MiscFlags = 0;
cbDesc.StructureByteStride = 0;
D3D11_SUBRESOURCE_DATA InitData;
InitData.pSysMem = &cbuff;
InitData.SysMemPitch = 0;
InitData.SysMemSlicePitch = 0;
dev->CreateBuffer(&cbDesc, &InitData,&g_pConstantBuffer11);
devcon->VSSetConstantBuffers(0, 1, &g_pConstantBuffer11);
On Render:
devcon->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST);
All possible exceptions are handled in this code. Just for clean code, they are removed. And there are no warnings or exceptions on Init and Render stage.
I can't even debug it because nothing being drawn to output. Also, is there any way to see output values from shaders?
cbuffer cbPerFrame : register(b0)
{
matrix world;
};
Is not used by the vertex shader anymore, but by the domain shader, however, it does not seem you are attaching it, so you should have :
devcon->VSSetConstantBuffers(0, 1, &g_pConstantBuffer11); //Vertex
devcon->DSSetConstantBuffers(0, 1, &g_pConstantBuffer11); //Domain
(Please note that you don't normally need to bind to vertex anymore if you use tesselation only).
Otherwise the pipeline will read a zero matrix.
I am trying to output more than one buffer from a shader - the general goal is to use it for GPGPU purposes. I've looked at this answer and got closer to the goal with this:
document.addEventListener("DOMContentLoaded", function() {
function main() {
const gl = document.querySelector('canvas').getContext('webgl2');
if (!gl) {
return alert("need WebGL2");
}
gl.canvas.width = 2;
gl.canvas.height = 2;
const vs = `
#version 300 es
in vec2 position;
void main(void) {
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
}
`;
const fs = `
#version 300 es
precision mediump float;
layout(location = 0) out vec4 outColor0;
layout(location = 1) out vec4 outColor1;
layout(location = 2) out vec4 outColor2;
layout(location = 3) out vec4 outColor3;
layout(location = 4) out vec4 outColor4;
layout(location = 5) out vec4 outColor5;
void main() {
// simplified for question purposes
outColor0 = vec4(1, 0, 0, 1);
outColor1 = vec4(0, 1, 0, 1);
outColor2 = vec4(0, 0, 1, 1);
outColor3 = vec4(1, 1, 0, 1);
outColor4 = vec4(1, 0, 1, 1);
outColor5 = vec4(0, 1, 1, 1);
}
`
const program = twgl.createProgram(gl, [vs, fs]);
const textures = [];
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
for (let i = 0; i < 6; ++i) {
const tex = gl.createTexture();
textures.push(tex);
gl.bindTexture(gl.TEXTURE_2D, tex);
const width = 2;
const height = 2;
const level = 0;
gl.texImage2D(gl.TEXTURE_2D, level, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
// attach texture to framebuffer
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, tex, level);
}
gl.viewport(0, 0, 2, 2);
// tell it we want to draw to all 4 attachments
gl.drawBuffers([
gl.COLOR_ATTACHMENT0,
gl.COLOR_ATTACHMENT1,
gl.COLOR_ATTACHMENT2,
gl.COLOR_ATTACHMENT3,
gl.COLOR_ATTACHMENT4,
gl.COLOR_ATTACHMENT5,
]);
// draw a single point
gl.useProgram(program);
{
const offset = 0;
const count = 1
gl.drawArrays(gl.TRIANGLE, 0, 4);
}
for (var l = 0; l < 6; l++) {
var pixels = new Uint8Array(gl.canvas.width * gl.canvas.height * 4);
gl.readBuffer(gl.COLOR_ATTACHMENT0 + l);
gl.readPixels(0, 0, gl.canvas.width, gl.canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
console.log(pixels.join(' '));
}
}
main();
})
However, the result is that only one pixel in each buffer gets set, so the output is:
0 0 0 0 255 0 0 255 0 0 0 0 0 0 0 0
0 0 0 0 0 255 0 255 0 0 0 0 0 0 0 0
0 0 0 0 0 0 255 255 0 0 0 0 0 0 0 0
0 0 0 0 255 255 0 255 0 0 0 0 0 0 0 0
0 0 0 0 255 0 255 255 0 0 0 0 0 0 0 0
0 0 0 0 0 255 255 255 0 0 0 0 0 0 0 0
rather than what I was hoping/expecting:
255 0 0 255 255 0 0 255 255 0 0 255 255 0 0 255
etc.
I was expecting that
outColor0 = vec4(1, 0, 0, 1);
is the equivalent to
gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
but clearly I am wrong.
So how do I get to the desired outcome - to be able to set each pixel on each of the buffers?
The code does not provide any vertex data even though it's asking it to draw 4 vertices. Further it's passing in gl.TRIANGLE which doesn't exist. It's gl.TRIANGLES with an S at the end. gl.TRIANGLE will be undefined which gets coerced into 0 which matches gl.POINTS
In the JavaScript console
> const gl = document.createElement('canvas').getContext('webgl2');
< undefined
> gl.TRIANGLE
< undefined
> gl.TRIANGLES
< 4
> gl.POINTS
< 0
To put it another way all the gl.CONSTANTS are just integer values. Instead of
gl.drawArrays(gl.TRIANGLES, offset, count)
you can just do this
gl.drawArrays(4, offset, count)
because gl.TRIANGLES = 4.
But you you didn't use gl.TRIANGLES you used gl.TRIANGLE (no S) so you effectively did this
gl.drawArrays(undefined, offset, count)
that was interpreted as
gl.drawArrays(0, offset, count)
0 = gl.POINTS so that's the same as
gl.drawArrays(gl.POINTS, offset, count)
The code then draws a single 1 pixel point 4 times at the same location because you called it with a count of 4
gl.drawArrays(gl.POINTS, 0, 4)
Nothing in your vertex shader changes each iteration so every iteration is going to do exactly the same thing. In this case it's going to draw a 1x1 pixel POINT at clip space position 0,0,0,1 which will end up being the bottom left pixel of the 2x2 pixels.
In any case you probably want to provide vertices but as a simple test if I add
gl_PointSize = 2.0;
to the vertex shader and change the draw call to
gl.drawArrays(gl.POINTS, 0, 1); // draw 1 point
Then it produces the results you expect. It draws a single 2x2 pixel POINT at clip space position 0,0,0,1
function main() {
const gl = document.querySelector('canvas').getContext('webgl2');
if (!gl) {
return alert("need WebGL2");
}
gl.canvas.width = 2;
gl.canvas.height = 2;
const vs = `
#version 300 es
in vec2 position;
void main(void) {
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
gl_PointSize = 2.0;
}
`;
const fs = `
#version 300 es
precision mediump float;
layout(location = 0) out vec4 outColor0;
layout(location = 1) out vec4 outColor1;
layout(location = 2) out vec4 outColor2;
layout(location = 3) out vec4 outColor3;
layout(location = 4) out vec4 outColor4;
layout(location = 5) out vec4 outColor5;
void main() {
// simplified for question purposes
outColor0 = vec4(1, 0, 0, 1);
outColor1 = vec4(0, 1, 0, 1);
outColor2 = vec4(0, 0, 1, 1);
outColor3 = vec4(1, 1, 0, 1);
outColor4 = vec4(1, 0, 1, 1);
outColor5 = vec4(0, 1, 1, 1);
}
`
const program = twgl.createProgram(gl, [vs, fs]);
const textures = [];
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
for (let i = 0; i < 6; ++i) {
const tex = gl.createTexture();
textures.push(tex);
gl.bindTexture(gl.TEXTURE_2D, tex);
const width = 2;
const height = 2;
const level = 0;
gl.texImage2D(gl.TEXTURE_2D, level, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
// attach texture to framebuffer
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, tex, level);
}
gl.viewport(0, 0, 2, 2);
// tell it we want to draw to all 4 attachments
gl.drawBuffers([
gl.COLOR_ATTACHMENT0,
gl.COLOR_ATTACHMENT1,
gl.COLOR_ATTACHMENT2,
gl.COLOR_ATTACHMENT3,
gl.COLOR_ATTACHMENT4,
gl.COLOR_ATTACHMENT5,
]);
// draw a single point
gl.useProgram(program); {
const offset = 0;
const count = 1
gl.drawArrays(gl.POINTS, 0, 1);
}
for (var l = 0; l < 6; l++) {
var pixels = new Uint8Array(gl.canvas.width * gl.canvas.height * 4);
gl.readBuffer(gl.COLOR_ATTACHMENT0 + l);
gl.readPixels(0, 0, gl.canvas.width, gl.canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
console.log(pixels.join(' '));
}
}
main();
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
You can try using webgl-lint which if I run with your original code will at least complain
Uncaught Error: https://greggman.github.io/webgl-lint/webgl-lint.js:2942: error in drawArrays(/UNKNOWN WebGL ENUM/ undefined, 0, 4): argument 0 is undefined
with WebGLProgram("unnamed") as current program
with the default vertex array bound
function main() {
const gl = document.querySelector('canvas').getContext('webgl2');
if (!gl) {
return alert("need WebGL2");
}
gl.canvas.width = 2;
gl.canvas.height = 2;
const vs = `
#version 300 es
in vec2 position;
void main(void) {
gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
}
`;
const fs = `
#version 300 es
precision mediump float;
layout(location = 0) out vec4 outColor0;
layout(location = 1) out vec4 outColor1;
layout(location = 2) out vec4 outColor2;
layout(location = 3) out vec4 outColor3;
layout(location = 4) out vec4 outColor4;
layout(location = 5) out vec4 outColor5;
void main() {
// simplified for question purposes
outColor0 = vec4(1, 0, 0, 1);
outColor1 = vec4(0, 1, 0, 1);
outColor2 = vec4(0, 0, 1, 1);
outColor3 = vec4(1, 1, 0, 1);
outColor4 = vec4(1, 0, 1, 1);
outColor5 = vec4(0, 1, 1, 1);
}
`
const program = twgl.createProgram(gl, [vs, fs]);
const textures = [];
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
for (let i = 0; i < 6; ++i) {
const tex = gl.createTexture();
textures.push(tex);
gl.bindTexture(gl.TEXTURE_2D, tex);
const width = 2;
const height = 2;
const level = 0;
gl.texImage2D(gl.TEXTURE_2D, level, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
// attach texture to framebuffer
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0 + i, gl.TEXTURE_2D, tex, level);
}
gl.viewport(0, 0, 2, 2);
// tell it we want to draw to all 4 attachments
gl.drawBuffers([
gl.COLOR_ATTACHMENT0,
gl.COLOR_ATTACHMENT1,
gl.COLOR_ATTACHMENT2,
gl.COLOR_ATTACHMENT3,
gl.COLOR_ATTACHMENT4,
gl.COLOR_ATTACHMENT5,
]);
// draw a single point
gl.useProgram(program); {
const offset = 0;
const count = 1
gl.drawArrays(gl.TRIANGLE, 0, 4);
}
for (var l = 0; l < 6; l++) {
var pixels = new Uint8Array(gl.canvas.width * gl.canvas.height * 4);
gl.readBuffer(gl.COLOR_ATTACHMENT0 + l);
gl.readPixels(0, 0, gl.canvas.width, gl.canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
console.log(pixels.join(' '));
}
}
main();
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
<script src="https://greggman.github.io/webgl-lint/webgl-lint.js" crossorigin="anonymous"></script>
I have a 3D Webgl scene. I am using Regl http://regl.party/ . Which is WebGL. So I am essentially writing straight GLSL.
This is a game project. I have an array of 3D positions [[x,y,z] ...] which are bullets, or projectiles. I want to draw these bullets as a simple cube, sphere, or particle. No requirement on the appearance.
How can I make shaders and a draw call for this without having to create a repeated duplicate set of geometry for the bullets?
Preferring an answer with a vert and frag shader example that demonstrates the expected data input and can be reverse engineered to handle the CPU binding layer
You create an regl command which encapsulates a bunch of data. You can then call it with an object.
Each uniform can take an optional function to supply its value. That function is passed a regl context as the first argument and then the object you passed as the second argument so you can call it multiple times with a different object to draw the same thing (same vertices, same shader) somewhere else.
var regl = createREGL()
const objects = [];
const numObjects = 100;
for (let i = 0; i < numObjects; ++i) {
objects.push({
x: rand(-1, 1),
y: rand(-1, 1),
speed: rand(.5, 1.5),
direction: rand(0, Math.PI * 2),
color: [rand(0, 1), rand(0, 1), rand(0, 1), 1],
});
}
function rand(min, max) {
return Math.random() * (max - min) + min;
}
const starPositions = [[0, 0, 0]];
const starElements = [];
const numPoints = 5;
for (let i = 0; i < numPoints; ++i) {
for (let j = 0; j < 2; ++j) {
const a = (i * 2 + j) / (numPoints * 2) * Math.PI * 2;
const r = 0.5 + j * 0.5;
starPositions.push([
Math.sin(a) * r,
Math.cos(a) * r,
0,
]);
}
starElements.push([
0, 1 + i * 2, 1 + i * 2 + 1,
]);
}
const drawStar = regl({
frag: `
precision mediump float;
uniform vec4 color;
void main () {
gl_FragColor = color;
}`,
vert: `
precision mediump float;
attribute vec3 position;
uniform mat4 mat;
void main() {
gl_Position = mat * vec4(position, 1);
}`,
attributes: {
position: starPositions,
},
elements: starElements,
uniforms: {
mat: (ctx, props) => {
const {viewportWidth, viewportHeight} = ctx;
const {x, y} = props;
const aspect = viewportWidth / viewportHeight;
return [.1 / aspect, 0, 0, 0,
0, .1, 0, 0,
0, 0, 0, 0,
x, y, 0, 1];
},
color: (ctx, props) => props.color,
}
})
regl.frame(function () {
regl.clear({
color: [0, 0, 0, 1]
});
objects.forEach((o) => {
o.direction += rand(-0.1, 0.1);
o.x += Math.cos(o.direction) * o.speed * 0.01;
o.y += Math.sin(o.direction) * o.speed * 0.01;
o.x = (o.x + 3) % 2 - 1;
o.y = (o.y + 3) % 2 - 1;
drawStar(o);
});
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/regl/1.3.11/regl.min.js"></script>
You can draw all of the bullets as point sprites, in which case you just need to provide the position and size of each bullet and draw them as GL_POINTS. Each “point” is rasterized to a square based on the output of your vertex shader (which runs once per point). Your fragment shader is called for each fragment in that square, and can color the fragment however it wants—with a flat color, by sampling a texture, or however else you want.
Or you can provide a single model for all bullets, a separate transform for each bullet, and draw them as instanced GL_TRIANGLES or GL_TRIANGLE_STRIP or whatever. Read about instancing on the OpenGL wiki.
Not a WebGL coder so read with prejudice...
Encode the vertexes in a texture
beware of clamping use texture format that does not clamp to <0.0,+1.0> like GL_LUMINANCE32F_ARB or use vertexes in that range only. To check for clamping use:
GLSL debug prints
Render single rectangle covering whole screen
and use the texture from #1 as input. This will ensure that a fragment shader is called for each pixel of the screen/view exactly once.
Inside fragment shader read the texture and check the distance of a fragment to your vertexes
based on it render your stuff or dicard() fragment... spheres are easy, but boxes and other shapes might be complicated to render based on the distance of vertex especially if they can be arbitrary oriented (which need additional info in the input texture).
To ease up this you can prerender them into some texture and use the distance as texture coordinates ...
This answer of mine is using this technique:
raytrace through 3D mesh
You can sometimes get away with using GL_POINTS with a large gl_PointSize and a customized fragment shader.
An example shown here using distance to point center for fragment alpha. (You could also just as well sample a texture)
The support for large point sizes might be limited though, so check that before deciding on this route.
var canvas = document.getElementById('cvs');
gl = canvas.getContext('webgl');
var vertices = [
-0.5, 0.75,0.0,
0.0, 0.5, 0.0,
-0.75,0.25,0.0,
];
var vertex_buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW);
gl.bindBuffer(gl.ARRAY_BUFFER, null);
var vertCode =
`attribute vec3 coord;
void main(void) {
gl_Position = vec4(coord, 1.0);
gl_PointSize = 50.0;
}`;
var vertShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertShader, vertCode);
gl.compileShader(vertShader);
var fragCode =
`void main(void) {
mediump float ds = distance(gl_PointCoord.xy, vec2(0.5,0.5))*2.0;
mediump vec4 fg_color=vec4(0.0, 0.0, 0.0,1.0- ds);
gl_FragColor = fg_color;
}`;
var fragShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragShader, fragCode);
gl.compileShader(fragShader);
var shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertShader);
gl.attachShader(shaderProgram, fragShader);
gl.linkProgram(shaderProgram);
gl.useProgram(shaderProgram);
gl.bindBuffer(gl.ARRAY_BUFFER, vertex_buffer);
var coord = gl.getAttribLocation(shaderProgram, "coord");
gl.vertexAttribPointer(coord, 3, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(coord);
gl.viewport(0,0,canvas.width,canvas.height);
gl.drawArrays(gl.POINTS, 0, 3);
<!doctype html>
<html>
<body>
<canvas width = "400" height = "400" id = "cvs"></canvas>
</body>
</html>
Using WebGL + GLSL, I'm trying to render a prism with the following vertex indices:
So far, though, I haven't been able to get this to render a prism -- I get instead a triangular plane:
var canvas,
gl,
fs,
vs,
glProgram,
vertexBuffer,
vertexIndexBuffer,
colorBuffer,
positionVal,
colorVal,
mvMatrix = mat4.create(),
pMatrix = mat4.create(),
angle = 0.01;
function initWebgl() {
canvas = document.querySelector('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
try {
gl = canvas.getContext('webgl')
gl.enable(gl.DEPTH_TEST)
gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT)
} catch(err) {
alert('Your browser does not support Webgl')
return;
}
// set the default background color
gl.clearColor(0.9, 0.9, 0.9, 1.0)
gl.clear(gl.COLOR_BUFFER_BIT)
}
function initCamera() {
// set camera area, fov, near clip, far clip, and translation
gl.viewport(0, 0, canvas.width, canvas.height)
mat4.perspective(45, canvas.width/canvas.height, 0.1, 100, pMatrix);
mat4.identity(mvMatrix);
mat4.translate(mvMatrix, [0, 0, -2.0]);
}
function initShaders() {
vs = buildShader('#shader-vs', gl.VERTEX_SHADER)
fs = buildShader('#shader-fs', gl.FRAGMENT_SHADER)
}
function buildShader(selector, type) {
var src = document.querySelector(selector).innerHTML;
var shader = gl.createShader(type)
gl.shaderSource(shader, src)
gl.compileShader(shader)
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.warn('Shader error', selector, gl.getShaderInfoLog(shader))
}
return shader;
}
function initProgram() {
glProgram = gl.createProgram()
gl.attachShader(glProgram, vs)
gl.attachShader(glProgram, fs)
gl.linkProgram(glProgram)
if (!gl.getProgramParameter(glProgram, gl.LINK_STATUS)) {
console.warn('Program link error')
}
gl.useProgram(glProgram)
}
function updatePositions() {
mat4.identity(mvMatrix)
mat4.translate(mvMatrix, [-1.0, -1.0, -7.0])
mat4.rotate(mvMatrix, angle, [0.0, 1.0, 0.0])
angle += 0.01;
}
function getBuffers() {
// vertex buffer
var vertexData = new Float32Array([
// front
-0.5, -0.5, 0.0,
0.5, -0.5, 0.0,
0, 0.5, 0.0,
// back
-0.5, -0.5, 5,
0.5, -0.5, 5,
0, 0.5, 5,
])
vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexData), gl.STATIC_DRAW)
// vertex index buffer - creates prism
var vertexIndices = new Uint16Array([
// front
0, 1, 2,
// right
1, 2, 4,
2, 4, 5,
// back
3, 4, 5,
// left
2, 3, 5,
0, 2, 3,
// bottom
0, 1, 3,
1, 3, 4,
])
vertexIndexBuffer = gl.createBuffer()
vertexIndexBuffer.number_vertex_points = vertexIndices.length;
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, vertexIndexBuffer)
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, vertexIndices, gl.STATIC_DRAW)
// color buffer
colorVal = colorVal || 0.5;
colorVal += 0.01;
var colorData = new Float32Array([
Math.sin(colorVal) + 1, Math.cos(colorVal) + 1, 1,
1, Math.sin(colorVal) + 1, 0,
Math.cos(colorVal) + 1, 1, 0,
Math.sin(colorVal) + 1, Math.cos(colorVal) + 1, 1,
1, Math.sin(colorVal) + 1, 0,
Math.cos(colorVal) + 1, 1, 0,
])
colorBuffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
gl.bufferData(gl.ARRAY_BUFFER, colorData, gl.DYNAMIC_DRAW)
}
function drawBuffers() {
// identify and bind vertex position attributes
var aVertexPosition = gl.getAttribLocation(glProgram, 'aVertexPosition')
gl.enableVertexAttribArray(aVertexPosition)
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, vertexIndexBuffer)
gl.vertexAttribPointer(aVertexPosition, 3, gl.FLOAT, false, 0.0, 0.0)
// identify and bind vertex color attributes
var aVertexColor = gl.getAttribLocation(glProgram, 'aVertexColor')
gl.enableVertexAttribArray(aVertexColor)
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
gl.vertexAttribPointer(aVertexColor, 3, gl.FLOAT, false, 0.0, 0.0)
// draw the data
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, vertexIndexBuffer)
gl.drawElements(gl.TRIANGLES, vertexIndexBuffer.number_vertex_points,
gl.UNSIGNED_SHORT, 0)
}
function getMatrixUniforms() {
glProgram.pMatrixUniform = gl.getUniformLocation(glProgram, 'uPMatrix')
glProgram.mvMatrixUniform = gl.getUniformLocation(glProgram, 'uMVMatrix')
}
function setMatrixUniforms() {
gl.uniformMatrix4fv(glProgram.pMatrixUniform, false, pMatrix)
gl.uniformMatrix4fv(glProgram.mvMatrixUniform, false, mvMatrix)
}
function render() {
updatePositions()
getBuffers()
drawBuffers()
setMatrixUniforms()
requestAnimationFrame(render, canvas)
}
initWebgl()
initCamera()
initShaders()
initProgram()
getMatrixUniforms()
render()
* {
margin: 0;
padding: 0;
}
body, html {
height: 100%;
width: 100%;
overflow: hidden;
background: skyblue;
}
<script src="https://rawgit.com/duhaime/955402641534b89babd41c8de8bc91f6/raw/5d86d54f7237f4cf2b206dcf0a3d453ba95acd1d/gl-matrix.js"></script>
<script id='shader-vs' type='x-shader/x-vertex'>
attribute vec3 aVertexPosition;
attribute vec3 aVertexColor;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
varying highp vec4 vColor;
void main() {
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
vColor = vec4(aVertexColor, 1.0);
}
</script>
<script id='shader-fs' type='x-shader/x-fragment'>
varying highp vec4 vColor;
void main() {
gl_FragColor = vColor;
}
</script>
<canvas />
Does anyone know what I can do to make the prism render? I'd be grateful for any pointers others can offer!
Whoops, I was passing the vertexIndexBuffer to the shaders to specify the positional attribute, but I should have passed the vertexBuffer to specify the positional attribute. This was the intended result:
var canvas,
gl,
fs,
vs,
glProgram,
vertexBuffer,
vertexIndexBuffer,
colorBuffer,
positionVal,
colorVal,
mvMatrix = mat4.create(),
pMatrix = mat4.create(),
angle = 0.01;
function initWebgl() {
canvas = document.querySelector('canvas');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
try {
gl = canvas.getContext('webgl')
gl.enable(gl.DEPTH_TEST)
gl.clear(gl.COLOR_BUFFER_BIT|gl.DEPTH_BUFFER_BIT)
} catch(err) {
alert('Your browser does not support Webgl')
return;
}
// set the default background color
gl.clearColor(0.9, 0.9, 0.9, 1.0)
gl.clear(gl.COLOR_BUFFER_BIT)
}
function initCamera() {
// set camera area, fov, near clip, far clip, and translation
gl.viewport(0, 0, canvas.width, canvas.height)
mat4.perspective(45, canvas.width/canvas.height, 0.1, 100, pMatrix);
mat4.identity(mvMatrix);
mat4.translate(mvMatrix, [0, 0, -2.0]);
}
function initShaders() {
vs = buildShader('#shader-vs', gl.VERTEX_SHADER)
fs = buildShader('#shader-fs', gl.FRAGMENT_SHADER)
}
function buildShader(selector, type) {
var src = document.querySelector(selector).innerHTML;
var shader = gl.createShader(type)
gl.shaderSource(shader, src)
gl.compileShader(shader)
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.warn('Shader error', selector, gl.getShaderInfoLog(shader))
}
return shader;
}
function initProgram() {
glProgram = gl.createProgram()
gl.attachShader(glProgram, vs)
gl.attachShader(glProgram, fs)
gl.linkProgram(glProgram)
if (!gl.getProgramParameter(glProgram, gl.LINK_STATUS)) {
console.warn('Program link error')
}
gl.useProgram(glProgram)
}
function updatePositions() {
mat4.identity(mvMatrix)
mat4.translate(mvMatrix, [-1.0, -1.0, -7.0])
mat4.rotate(mvMatrix, angle, [0.0, 1.0, 0.0])
angle += 0.01;
}
function getBuffers() {
// vertex buffer
var vertexData = new Float32Array([
// front
-0.5, -0.5, 0.0,
0.5, -0.5, 0.0,
0, 0.5, 0.0,
// back
-0.5, -0.5, 5,
0.5, -0.5, 5,
0, 0.5, 5,
])
vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertexData), gl.STATIC_DRAW)
// vertex index buffer - creates prism
var vertexIndices = new Uint16Array([
// front
0, 1, 2,
// right
1, 2, 4,
2, 4, 5,
// back
3, 4, 5,
// left
2, 3, 5,
0, 2, 3,
// bottom
0, 1, 3,
1, 3, 4,
])
vertexIndexBuffer = gl.createBuffer()
vertexIndexBuffer.number_vertex_points = vertexIndices.length;
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, vertexIndexBuffer)
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, vertexIndices, gl.STATIC_DRAW)
// color buffer
colorVal = colorVal || 0.5;
colorVal += 0.01;
var colorData = new Float32Array([
Math.sin(colorVal) + 1, Math.cos(colorVal) + 1, 1,
1, Math.sin(colorVal) + 1, 0,
Math.cos(colorVal) + 1, 1, 0,
Math.sin(colorVal) + 1, Math.cos(colorVal) + 1, 1,
1, Math.sin(colorVal) + 1, 0,
Math.cos(colorVal) + 1, 1, 0,
])
colorBuffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
gl.bufferData(gl.ARRAY_BUFFER, colorData, gl.DYNAMIC_DRAW)
}
function drawBuffers() {
// identify and bind vertex position attributes
var aVertexPosition = gl.getAttribLocation(glProgram, 'aVertexPosition')
gl.enableVertexAttribArray(aVertexPosition)
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer)
gl.vertexAttribPointer(aVertexPosition, 3, gl.FLOAT, false, 0.0, 0.0)
// identify and bind vertex color attributes
var aVertexColor = gl.getAttribLocation(glProgram, 'aVertexColor')
gl.enableVertexAttribArray(aVertexColor)
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer)
gl.vertexAttribPointer(aVertexColor, 3, gl.FLOAT, false, 0.0, 0.0)
// draw the data
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, vertexIndexBuffer)
gl.drawElements(gl.TRIANGLES, vertexIndexBuffer.number_vertex_points,
gl.UNSIGNED_SHORT, 0)
}
function getMatrixUniforms() {
glProgram.pMatrixUniform = gl.getUniformLocation(glProgram, 'uPMatrix')
glProgram.mvMatrixUniform = gl.getUniformLocation(glProgram, 'uMVMatrix')
}
function setMatrixUniforms() {
gl.uniformMatrix4fv(glProgram.pMatrixUniform, false, pMatrix)
gl.uniformMatrix4fv(glProgram.mvMatrixUniform, false, mvMatrix)
}
function render() {
updatePositions()
getBuffers()
drawBuffers()
setMatrixUniforms()
requestAnimationFrame(render, canvas)
}
initWebgl()
initCamera()
initShaders()
initProgram()
getMatrixUniforms()
render()
* {
margin: 0;
padding: 0;
}
body, html {
height: 100%;
width: 100%;
overflow: hidden;
background: skyblue;
}
<script src="https://rawgit.com/duhaime/955402641534b89babd41c8de8bc91f6/raw/5d86d54f7237f4cf2b206dcf0a3d453ba95acd1d/gl-matrix.js"></script>
<script id='shader-vs' type='x-shader/x-vertex'>
attribute vec3 aVertexPosition;
attribute vec3 aVertexColor;
uniform mat4 uMVMatrix;
uniform mat4 uPMatrix;
varying highp vec4 vColor;
void main() {
gl_Position = uPMatrix * uMVMatrix * vec4(aVertexPosition, 1.0);
vColor = vec4(aVertexColor, 1.0);
}
</script>
<script id='shader-fs' type='x-shader/x-fragment'>
varying highp vec4 vColor;
void main() {
gl_FragColor = vColor;
}
</script>
<canvas />
I've got a function that basically creates different instance buffers into an array for me to use in my DrawIndexedInstanced call.
But when I pass the vertex buffer and instance buffer through to my shader, my instance data is completely lost when the shader goes to use it, so none of my objects are being relocated and are thus all rendering in the same place.
I've been looking at this for hours and literally cannot find anything that is helpful.
Creating the Vertex shader input layout:
D3D11_INPUT_ELEMENT_DESC solidColorLayout[] =
{
//Vertex Buffer
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
{ "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
//Instance buffer
{ "INSTANCEPOS", 0, DXGI_FORMAT_R32G32B32_FLOAT, 1, 0, D3D11_INPUT_PER_INSTANCE_DATA, 1 },
{ "INSTANCEROT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 1, 12, D3D11_INPUT_PER_INSTANCE_DATA, 1 },
{ "INSTANCESCA", 0, DXGI_FORMAT_R32G32B32_FLOAT, 1, 24, D3D11_INPUT_PER_INSTANCE_DATA, 1 },
{ "INSTANCETEX", 0, DXGI_FORMAT_R32_FLOAT, 1, 36, D3D11_INPUT_PER_INSTANCE_DATA, 1 }
};
Creating an instance buffer (called multiple times per frame, to create all necessary buffers):
void GameManager::CreateInstanceBuffer(ID3D11Buffer** buffer, Mesh* mesh, std::vector<Instance> instances)
{
D3D11_BUFFER_DESC instBuffDesc;
ZeroMemory(&instBuffDesc, sizeof(instBuffDesc));
instBuffDesc.Usage = D3D11_USAGE_DEFAULT;
instBuffDesc.ByteWidth = sizeof(Instance) * instances.size();
instBuffDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
instBuffDesc.CPUAccessFlags = 0;
instBuffDesc.MiscFlags = 0;
instBuffDesc.StructureByteStride = 0;
int i = sizeof(Instance);
D3D11_SUBRESOURCE_DATA instData;
ZeroMemory(&instData, sizeof(instData));
instData.pSysMem = &instances;
instData.SysMemPitch = 0;
instData.SysMemSlicePitch = 0;
CheckFailWithError(dxManager.GetDevice()->CreateBuffer(&instBuffDesc, &instData, buffer),
"An error occurred whilst building an instance buffer",
"[GameManager]");
meshBuffers.push_back(mesh->GetBuffer(VERTEX_BUFFER));
}
The draw command:
dxManager.GetContext()->DrawIndexedInstanced(instanceIndexCounts[buffer], instanceCounts[buffer], 0, 0, 0);
The shader:
cbuffer cbChangesEveryFrame : register(b0)
{
matrix worldMatrix;
};
cbuffer cbNeverChanges : register(b1)
{
matrix viewMatrix;
};
cbuffer cbChangeOnResize : register(b2)
{
matrix projMatrix;
};
struct VS_Input
{
float4 pos : POSITION;
float2 tex0 : TEXCOORD0;
float4 instancePos : INSTANCEPOS;
float4 instanceRot : INSTANCEROT;
float4 instanceSca : INSTANCESCA;
float instanceTex : INSTANCETEX;
};
PS_Input VS_Main(VS_Input vertex)
{
PS_Input vsOut = (PS_Input)0;
vsOut.pos = mul(vertex.pos + vertex.instancePos, worldMatrix);
vsOut.pos = mul(vsOut.pos, viewMatrix);
vsOut.pos = mul(vsOut.pos, projMatrix);
vsOut.tex0 = vertex.tex0;
return vsOut;
}
I've used the graphics debugger built into Visual Studio. Initially it appeared to be assigning variables in the Vertex shader back to front, however removing APPEND_ALIGNED_ELEMENT from the AlignedByteOffset has fixed that, however the per-instance data seems to be corrupt and is not getting recieved.
If there is anything else you need let me know and I'll update the post as necessary.
The problem lies in your subresource data.
instData.pSysMem = &instances;
You are not specifying which offset to read the memory from. Try using
instData.pSysMem = &instances[0];
or
instData.pSysMem = &instances.at(0);
That clarifies where to start reading memory from and will hopefully fix your issue.