Skip to content

Commit

Permalink
Wrote shader picker
Browse files Browse the repository at this point in the history
  • Loading branch information
Naviary2 committed Dec 26, 2024
1 parent 6f24d7a commit 1615808
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 34 deletions.
4 changes: 2 additions & 2 deletions src/client/scripts/esm/game/rendering/buffermodel.js
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ function renderPreppedModel(program, position = [0,0,0], scale = [1,1,1], vertex
* Sends a custom-specified uniform to the gpu before rendering.
* ASSUMES the provided program has been set already with gl.useProgram()!
* @param {ShaderProgram} program - The shader program
* @param {string} name - The name of the uniform, for example, `uVertexColor`.
* @param {string} name - The name of the uniform, for example, `tintColor`.
* @param {number[] | Float32Array | number} value - The value of the uniform, for example, `[1,0,0,1]`.
*/
function sendCustomUniformToGPU(program, name, value) {
Expand Down Expand Up @@ -367,7 +367,7 @@ function BufferModel(program, data, stride, mode, texture, prepDrawFunc) { // da
* Applies any custom uniform values before rendering.
* @param {number[]} position - The positional translation
* @param {number[]} scale - The scaling transformation
* @param {Object} [customUniformValues] - If applicable, an object containing any custom uniform values. For example, `{ uVertexColor: [1,0,0,1] }` - This particular uniform is used for the tintedTextureProgram.
* @param {Object} [customUniformValues] - If applicable, an object containing any custom uniform values. For example, `{ tintColor: [1,0,0,1] }` - This particular uniform is used for the tintedTextureProgram.
*/
this.render = function(position, scale, customUniformValues) { // [0,0,0], [1,1,1] Can be undefined, render will use defaults.
// Must be called before every time we render the model.
Expand Down
14 changes: 7 additions & 7 deletions src/client/scripts/esm/game/rendering/camera.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ let screenBoundingBox_devMode;

/** Contains the matrix for transforming our camera to look like it's in perspective.
* This ONLY needs to update on the gpu whenever the screen size changes. */
let projectionMatrix; // Same for every shader program
let projMatrix; // Same for every shader program

/** Contains the camera's position and rotation, updated once per frame on the gpu.
*
Expand Down Expand Up @@ -164,7 +164,7 @@ function init() {
// Inits the matrix uniforms: viewMatrix (camera) & projMatrix
function initMatrixes() {

projectionMatrix = mat4.create(); // Same for every shader program
projMatrix = mat4.create(); // Same for every shader program

initPerspective(); // Initiates perspective, including the projection matrix

Expand Down Expand Up @@ -255,15 +255,15 @@ function sendViewMatrixToGPU() {

/** Inits the projection matrix uniform and sends that over to the gpu for each of our shader programs. */
function initProjMatrix() {
mat4.perspective(projectionMatrix, fieldOfView, aspect, zNear, zFar);
// Send the projectionMatrix to the gpu
mat4.perspective(projMatrix, fieldOfView, aspect, zNear, zFar);
// Send the projMatrix to the gpu
for (const programName in shaders.programs) { // Iterate over an object's properties
/** @type {ShaderProgram} */
const program = shaders.programs[programName];
const projMatrixLocation = program.uniformLocations.projectionMatrix;
if (projMatrixLocation === undefined) continue; // This shader program doesn't have the projectionMatrix uniform, skip.
const projMatrixLocation = program.uniformLocations.projMatrix;
if (projMatrixLocation === undefined) continue; // This shader program doesn't have the projMatrix uniform, skip.
gl.useProgram(program.program);
gl.uniformMatrix4fv(projMatrixLocation, false, projectionMatrix);
gl.uniformMatrix4fv(projMatrixLocation, false, projMatrix);
}

frametracker.onVisualChange();
Expand Down
2 changes: 1 addition & 1 deletion src/client/scripts/esm/game/rendering/pieces.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ function renderPieces(gamefile) {

modelToUse.render(position, scale);
// Use this line when rendering with the tinted texture shader program.
// modelToUse.render(position, scale, { uVertexColor: [1,0,0, 1] }); // Specifies the tint uniform value before rendering
// modelToUse.render(position, scale, { tintColor: [1,0,0, 1] }); // Specifies the tint uniform value before rendering
}

/** Renders a semi-transparent piece at the specified coordinates. */
Expand Down
111 changes: 87 additions & 24 deletions src/client/scripts/esm/game/rendering/shaders.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ import camera from './camera.js';
* to make it look the same size on retina displays as non-retina? */
const pointSize = 1;

/**
* Shader uniforms that MUST BE SET every before every single render call,
* or the value from the previous render call's setting will bleed through.
*
* For example, the position of the last rendered item,
* or the tint-color of the last rendered item.
*
* These uniforms should be optional, BUT if they are not provided,
* we have to set them to a default value, before rendering, to avoid this!
*/
const manualUniforms = ['worldMatrix','tintColor'];


/** The shader programs at our disposal.
* The world matrix uniform needs to be set with each draw call,
* it transforms and rotates the bound mesh. */
Expand Down Expand Up @@ -66,7 +79,7 @@ const programs = {
*
* Each point must contain the positional data (2 or 3 numbers),
* followed by the texture data (2 numbers).
* Set the tint by updating the uniform `uVertexColor` before rendering by using gl.uniform4fv(),
* Set the tint by updating the uniform `tintColor` before rendering by using gl.uniform4fv(),
* or just by sending the uniform value into {@link BufferModel.render}
* @type {ShaderProgram}
*/
Expand Down Expand Up @@ -124,11 +137,11 @@ function createColorProgram() {
return {
program,
attribLocations: {
vertexPosition: gl.getAttribLocation(program, 'aVertexPosition'),
vertexColor: gl.getAttribLocation(program, 'aVertexColor')
position: gl.getAttribLocation(program, 'aVertexPosition'),
color: gl.getAttribLocation(program, 'aVertexColor')
},
uniformLocations: {
projectionMatrix: gl.getUniformLocation(program, 'uProjMatrix'),
projMatrix: gl.getUniformLocation(program, 'uProjMatrix'),
viewMatrix: gl.getUniformLocation(program, 'uViewMatrix'),
worldMatrix: gl.getUniformLocation(program, 'uWorldMatrix')
},
Expand All @@ -147,7 +160,7 @@ function createColorProgram_Instanced() {
const vsSource = `#version 300 es
in vec4 aVertexPosition;
in vec4 aVertexColor;
in vec4 aInstanceOffset; // Per-instance position offset attribute
in vec4 aInstancePosition; // Per-instance position offset attribute
uniform mat4 uWorldMatrix;
uniform mat4 uViewMatrix;
Expand All @@ -157,7 +170,7 @@ function createColorProgram_Instanced() {
void main() {
// Add the instance offset to the vertex position
vec4 transformedVertexPosition = vec4(aVertexPosition.xyz + aInstanceOffset.xyz, 1.0);
vec4 transformedVertexPosition = vec4(aVertexPosition.xyz + aInstancePosition.xyz, 1.0);
gl_Position = uProjMatrix * uViewMatrix * uWorldMatrix * transformedVertexPosition;
vColor = aVertexColor;
Expand All @@ -180,12 +193,12 @@ function createColorProgram_Instanced() {
return {
program,
attribLocations: {
vertexPosition: gl.getAttribLocation(program, 'aVertexPosition'),
vertexColor: gl.getAttribLocation(program, 'aVertexColor'),
instanceOffset: gl.getAttribLocation(program, 'aInstanceOffset')
position: gl.getAttribLocation(program, 'aVertexPosition'),
color: gl.getAttribLocation(program, 'aVertexColor'),
instanceposition: gl.getAttribLocation(program, 'aInstancePosition')
},
uniformLocations: {
projectionMatrix: gl.getUniformLocation(program, 'uProjMatrix'),
projMatrix: gl.getUniformLocation(program, 'uProjMatrix'),
viewMatrix: gl.getUniformLocation(program, 'uViewMatrix'),
worldMatrix: gl.getUniformLocation(program, 'uWorldMatrix')
},
Expand Down Expand Up @@ -233,11 +246,11 @@ function createTextureProgram() {
return {
program,
attribLocations: {
vertexPosition: gl.getAttribLocation(program, 'aVertexPosition'),
textureCoord: gl.getAttribLocation(program, 'aTextureCoord'),
position: gl.getAttribLocation(program, 'aVertexPosition'),
texcoord: gl.getAttribLocation(program, 'aTextureCoord'),
},
uniformLocations: {
projectionMatrix: gl.getUniformLocation(program, 'uProjMatrix'),
projMatrix: gl.getUniformLocation(program, 'uProjMatrix'),
viewMatrix: gl.getUniformLocation(program, 'uViewMatrix'),
worldMatrix: gl.getUniformLocation(program, 'uWorldMatrix'),
uSampler: gl.getUniformLocation(program, 'uSampler'),
Expand Down Expand Up @@ -293,12 +306,12 @@ function createColoredTextureProgram() {
return {
program,
attribLocations: {
vertexPosition: gl.getAttribLocation(program, 'aVertexPosition'),
textureCoord: gl.getAttribLocation(program, 'aTextureCoord'),
vertexColor: gl.getAttribLocation(program, 'aVertexColor')
position: gl.getAttribLocation(program, 'aVertexPosition'),
texcoord: gl.getAttribLocation(program, 'aTextureCoord'),
color: gl.getAttribLocation(program, 'aVertexColor')
},
uniformLocations: {
projectionMatrix: gl.getUniformLocation(program, 'uProjMatrix'),
projMatrix: gl.getUniformLocation(program, 'uProjMatrix'),
viewMatrix: gl.getUniformLocation(program, 'uViewMatrix'),
worldMatrix: gl.getUniformLocation(program, 'uWorldMatrix'),
uSampler: gl.getUniformLocation(program, 'uSampler')
Expand Down Expand Up @@ -333,11 +346,11 @@ function createTintedTextureProgram() {
const fsSource = `
varying lowp vec2 vTextureCoord;
uniform lowp vec4 uVertexColor;
uniform lowp vec4 uTintColor;
uniform sampler2D uSampler;
void main(void) {
gl_FragColor = texture2D(uSampler, vTextureCoord) * uVertexColor;
gl_FragColor = texture2D(uSampler, vTextureCoord) * uTintColor;
}
`;

Expand All @@ -347,12 +360,12 @@ function createTintedTextureProgram() {
const tintedTextureProgram = {
program,
attribLocations: {
vertexPosition: gl.getAttribLocation(program, 'aVertexPosition'),
textureCoord: gl.getAttribLocation(program, 'aTextureCoord'),
position: gl.getAttribLocation(program, 'aVertexPosition'),
texcoord: gl.getAttribLocation(program, 'aTextureCoord'),
},
uniformLocations: {
uVertexColor: gl.getUniformLocation(program, 'uVertexColor'),
projectionMatrix: gl.getUniformLocation(program, 'uProjMatrix'),
tintColor: gl.getUniformLocation(program, 'uTintColor'),
projMatrix: gl.getUniformLocation(program, 'uProjMatrix'),
viewMatrix: gl.getUniformLocation(program, 'uViewMatrix'),
worldMatrix: gl.getUniformLocation(program, 'uWorldMatrix'),
uSampler: gl.getUniformLocation(program, 'uSampler')
Expand Down Expand Up @@ -418,7 +431,57 @@ function createShader(type, sourceText) { // type: gl.VERTEX_SHADER / gl.FRAGMEN
return shader;
}


/**
* Picks a compatible shader that will work with all the provided attributes and uniforms.
*
* Uniforms you NEVER have to provide are [projMatrix, viewMatrix, worldMatrix, uSampler],
* because those are either present in every shader already, OR the uSampler uniform
* is assumed if you're using the 'texcoord' attribute.
*
* An example of a uniform you WOULD specify is 'tintColor'.
*
* @param {string[]} attributes - A list of all attributes we need to use. (e.g. `['position','color']` for vertex data that doesn't use a texture)
* @param {string[]} [uniforms] - Optional. Only provide if you need to use a uniform that is not one of the assumed [projMatrix, viewMatrix, worldMatrix, uSampler]
*/
function shaderPicker(attributes, uniforms = []) {

let compatibleShaders = Object.values(programs);

// Iterate through all existing shaders, check to see if they support each of our attributes and uniforms.
attributes.forEach((attrib) => {
compatibleShaders = compatibleShaders.filter((program) => program.attribLocations[attrib] !== undefined);
});
uniforms.forEach((uniform) => {
compatibleShaders = compatibleShaders.filter((program) => program.uniformLocations[uniform] !== undefined);
});

if (compatibleShaders.length === 0) throw new Error(`Cannot find a shader compatible with the requested attributes and uniforms: ${JSON.stringify(attributes)}, ${JSON.stringify(uniforms)}`);

// What if there are multiple shaders compatible?
// Use the least complex one (lowest number of attributes and uniforms)

const leastComplexShader = compatibleShaders.reduce((leastComplex, current) => {
const leastComplexComplexity = getShaderComplexity(leastComplex);
const currentComplexity = getShaderComplexity(current);
if (leastComplexComplexity === currentComplexity) throw new Error(`Shaders have the same level of complexity, can't pick which one to use! Requested attributes and uniforms: ${JSON.stringify(attributes)}, ${JSON.stringify(uniforms)}`);
return currentComplexity < leastComplexComplexity;
});

console.log(`Chose shader with attributes and uniforms: ${JSON.stringify(attributes)}, ${JSON.stringify(uniforms)}\nTo use for requested attributes and uniforms: ${JSON.stringify(attributes)}, ${JSON.stringify(uniforms)}`);

return leastComplexShader;
}

function getShaderComplexity(program) {
return Object.keys(program.attribLocations).length + Object.keys(program.uniformLocations).length;
}



export default {
initPrograms,
programs
programs,
shaderPicker,
manualUniforms,
};

0 comments on commit 1615808

Please sign in to comment.