|
| 1 | +--- |
| 2 | +title: Aperture Tutorial (for legacy devs) |
| 3 | +description: Temporary documentation for the upcoming Aperture pipeline |
| 4 | +tableOfContents: true |
| 5 | +sidebar: |
| 6 | + label: Legacy -> Aperture Tutorial |
| 7 | + order: 1 |
| 8 | +--- |
| 9 | + |
| 10 | +:::note[Note] |
| 11 | +This guide is intended for those already familiar with shader development. |
| 12 | +::: |
| 13 | + |
| 14 | +:::caution[Warning] |
| 15 | +**Aperture is still in early development and not for public use.** Things can and will change, and parts of this section may be incomplete or outright incorrect. |
| 16 | +::: |
| 17 | + |
| 18 | +Aperture is a new take on shader packs utilizing TypeScript to create an extensible pipeline. |
| 19 | + |
| 20 | +This serves as temporary documentation whilst Aperture is being developed. |
| 21 | + |
| 22 | +## `pack.ts` |
| 23 | + |
| 24 | +The core of the pipeline; `pack.ts` is where you start registering programs. Unlike in OptiFine/Iris, there is no "*default*" setup; you **must** create one. |
| 25 | + |
| 26 | +The required parts of `pack.ts` are composed of two separate functions: `configureRenderer` and `configurePipeline`. |
| 27 | + |
| 28 | +### `configureRenderer` |
| 29 | + |
| 30 | +`configureRenderer` is where you configure the aspects of how Minecraft's default rendering should change to accomodate your pipeline. This is where you define: |
| 31 | + |
| 32 | +- If you will use shadow maps, and if so, their settings |
| 33 | +- If you want sun tilt, ambient occlusion, or directional shade |
| 34 | +- If you want point light shadows (documentation not ready) |
| 35 | + |
| 36 | +An example of `configureRenderer`: |
| 37 | + |
| 38 | +```ts |
| 39 | +function configureRenderer(config : RendererConfig) : void { |
| 40 | + config.disableShade = true; |
| 41 | + config.ambientOcclusionLevel = 0.0; |
| 42 | + |
| 43 | + config.render.sun = false; |
| 44 | + |
| 45 | + config.shadow.enabled = true; |
| 46 | + config.shadow.resolution = 1024; |
| 47 | +} |
| 48 | +``` |
| 49 | + |
| 50 | +### `configurePipeline` |
| 51 | + |
| 52 | +The actual meat of the pipeline; this is where you will configure the following: |
| 53 | + |
| 54 | +- Textures |
| 55 | +- Buffers |
| 56 | +- Object shaders (previously known as `gbuffer` programs) |
| 57 | +- Command lists (containing composite/compute shaders) |
| 58 | + |
| 59 | +There is no explicit order or way to do this, so we will cover these in order. |
| 60 | + |
| 61 | +First, let's define it. |
| 62 | + |
| 63 | +```ts |
| 64 | +function configurePipeline(pipeline : PipelineConfig) : void { |
| 65 | + |
| 66 | +} |
| 67 | +``` |
| 68 | + |
| 69 | +### Textures |
| 70 | + |
| 71 | +In Aperture, the only textures provided to you by default are depth textures; that being `mainDepthTex` (previously `depthtex0`) and `solidDepthTex` (previously `depthtex1`). |
| 72 | + |
| 73 | +All other textures, including color textures, must be created and provided by you. |
| 74 | + |
| 75 | +For the following, we will use the predefined variables `screenWidth` and `screenHeight`. |
| 76 | + |
| 77 | +Let's create two basic RGBA8 textures. (The reason for creating two will be explained later.) |
| 78 | + |
| 79 | +```ts |
| 80 | +let mainTex = pipeline.createTexture('mainTexture') // The string provided here will be what is used to access this texture in shader code. |
| 81 | + .format(Format.RGBA8) |
| 82 | + .width(screenWidth) |
| 83 | + .height(screenHeight) |
| 84 | + .build(); |
| 85 | + |
| 86 | +let finalTex = pipeline.createTexture('finalTexture') |
| 87 | + .format(Format.RGBA8) |
| 88 | + .width(screenWidth) |
| 89 | + .height(screenHeight) |
| 90 | + .build(); |
| 91 | +``` |
| 92 | + |
| 93 | +Remember that we created this, we will use this later. |
| 94 | + |
| 95 | +In Aperture, there are many other types of textures, including array textures, PNG textures, and raw data textures. These will not be covered here. |
| 96 | + |
| 97 | +**Aperture does not contain buffer flipping. You cannot read and write to a texture in the same pass** (unless using `image` variables.) |
| 98 | + |
| 99 | +### Combination pass |
| 100 | + |
| 101 | +Let's get this out of the way first. The "combination pass" is the final stage of rendering, and is required; it will state what is sent as the final image. |
| 102 | + |
| 103 | +Let's make a dummy one quickly. |
| 104 | + |
| 105 | +```ts |
| 106 | +pipeline.createCombinationPass("programs/combination.fsh").build(); |
| 107 | +``` |
| 108 | + |
| 109 | +Create `programs/combination.fsh`, and put this in. |
| 110 | + |
| 111 | +```glsl |
| 112 | +#version 460 core |
| 113 | +
|
| 114 | +uniform sampler2D finalTexture; |
| 115 | +in vec2 uv; |
| 116 | +
|
| 117 | +layout(location = 0) out vec4 finalColor; |
| 118 | +
|
| 119 | +void main() { |
| 120 | + finalColor = texture(finalTexture, uv); |
| 121 | +} |
| 122 | +``` |
| 123 | +### Object shaders |
| 124 | + |
| 125 | +Previously known as `gbuffer_` shaders, these are the shaders run on world geometry. These differ in many ways from in Optifine/Iris. |
| 126 | + |
| 127 | +Time to define our first shader. |
| 128 | + |
| 129 | +```ts |
| 130 | +pipeline.createObjectShader("basic", Usage.BASIC) |
| 131 | + .vertex("programs/basic.vsh") |
| 132 | + .fragment("programs/basic.fsh") |
| 133 | + .target(0, mainTex) |
| 134 | + .compile(); |
| 135 | +``` |
| 136 | + |
| 137 | +We have just defined an object shader for the `BASIC` program usage; this will be the shader all others fall back to when there isn't a more specialized alternative. |
| 138 | + |
| 139 | +We have defined that output 0 will write to the `mainTex` we specified earlier. |
| 140 | + |
| 141 | +#### Vertex shader |
| 142 | + |
| 143 | +Time to create the vertex shader. This is quite different than what you are used to. |
| 144 | + |
| 145 | +```glsl |
| 146 | +#version 460 core |
| 147 | +
|
| 148 | +out vec2 uv; |
| 149 | +out vec2 light; |
| 150 | +out vec4 color; |
| 151 | +
|
| 152 | +void iris_emitVertex(inout VertexData data) { |
| 153 | + data.clipPos = iris_projectionMatrix * iris_modelViewMatrix * data.modelPos; |
| 154 | +} |
| 155 | +
|
| 156 | +void iris_sendParameters(VertexData data) { |
| 157 | + uv = data.uv; |
| 158 | + light = data.light; |
| 159 | + color = vec4(data.color.rgb * data.ao, data.color.a); // Ambient occlusion is split by default in Aperture. |
| 160 | +} |
| 161 | +``` |
| 162 | + |
| 163 | +So. What is going on here? Let's break this down. |
| 164 | + |
| 165 | +First, note the two separate functions. `iris_emitVertex` contains an `inout` copy of the VertexData, hinting that it should be edited, while `iris_sendParameters` does not. |
| 166 | + |
| 167 | +Indeed, the job of `iris_emitVertex` is to *modify the vertex position.* It contains two jobs: modifying the position as the shader wishes, and converting said position to clip space. |
| 168 | + |
| 169 | +Why are these two separate functions? Simply put, because `iris_emitVertex` *can be called more than once per vertex*, while `iris_sendParameters` is guaranteed to *only be called once*. As the names hint, you should send vertex parameters within `sendParameters` for this reason. |
| 170 | +However, this is not a hard limit; if needed, you can mix and match these. Just beware the unexpected. |
| 171 | + |
| 172 | +Small note: If you've used Optifine, you may know that you need to use texture matrices to put `light` into 0-1 range. This is not needed in Aperture, and this range is the default range. |
| 173 | + |
| 174 | +#### Fragment shader |
| 175 | + |
| 176 | +Time for the other side. |
| 177 | + |
| 178 | +```glsl |
| 179 | +#version 460 core |
| 180 | +
|
| 181 | +in vec2 uv; |
| 182 | +in vec2 light; |
| 183 | +in vec4 color; |
| 184 | +
|
| 185 | +layout(location = 0) out vec4 outColor; // Remember when we put location 0 in `pack.ts`? This is where it's used. |
| 186 | +
|
| 187 | +void iris_emitFragment() { |
| 188 | + vec2 mUV = uv, mLight = light; |
| 189 | + vec4 mColor = color; |
| 190 | +
|
| 191 | + iris_modifyBase(mUV, mColor, mLight); |
| 192 | +
|
| 193 | + outColor = iris_sampleBaseTex(mUV) * iris_sampleLightmap(mLight) * mColor; |
| 194 | +
|
| 195 | + if (iris_discardFragment(outColor)) discard; |
| 196 | +} |
| 197 | +``` |
| 198 | + |
| 199 | +Less going on here, but still notable. Let's go through it. |
| 200 | + |
| 201 | +First of all, `main` is now `iris_emitFragment`. Second of all, you must define your outputs; no more `gl_FragData`. |
| 202 | + |
| 203 | +Now, these copies of the outputs; why do we need them? Simply, to allow greater mod support. `iris_modifyBase` is a default hook for mods to modify per-fragment data, without impacting the final image significantly. |
| 204 | + |
| 205 | +(No, you cannot edit the `in` values directly; they're read only.) |
| 206 | + |
| 207 | +**It is always recommended, but not required to have this hook when possible.** |
| 208 | + |
| 209 | +Second, `iris_discardFragment`. Alpha testing (the act of transparent objects being transparent) is not implicit in Aperture; this simple `if` statement takes care of all those situations. |
| 210 | + |
| 211 | +Third, notice the lack of uniforms for sampling the base texture and lightmap. These are handled using built-in functions instead; examples include: |
| 212 | +- `iris_sampleBaseTex` |
| 213 | +- `iris_sampleLightmap` |
| 214 | +- `iris_sampleNormalMap` |
| 215 | +- `iris_sampleSpecularMap` |
| 216 | + |
| 217 | +### Command lists (composite/compute) |
| 218 | + |
| 219 | +The functions of composite and compute shaders have been merged into a single "idea", known as a command list. These "lists" run at certain points in the pipeline, similar to `composite`, `deferred`, and `prepare` stages. |
| 220 | + |
| 221 | +Command lists are also capable of having sub-lists, which is helpful for debugging. |
| 222 | + |
| 223 | +Let's create a basic command list with a single composite. Computes will not be covered in this page. |
| 224 | + |
| 225 | +```ts |
| 226 | +let postRenderList = pipeline.forStage(Stage.POST_RENDER); |
| 227 | + |
| 228 | +postRenderList.createComposite("brighten") |
| 229 | + .fragment("programs/brighten.fsh") |
| 230 | + .target(0, finalTex) |
| 231 | + .compile(); |
| 232 | + |
| 233 | +postRenderList.end(); |
| 234 | +``` |
| 235 | + |
| 236 | +(You are not required to have a vertex shader for composites; although it is allowed, we will not cover them.) |
| 237 | + |
| 238 | +Let's write it! Unlike with object shaders, these don't contain much special syntax. |
| 239 | + |
| 240 | +```glsl |
| 241 | +#version 460 core |
| 242 | +
|
| 243 | +uniform sampler2D mainTexture; |
| 244 | +
|
| 245 | +in vec2 uv; |
| 246 | +
|
| 247 | +layout(location = 0) out vec4 finalTex; |
| 248 | +
|
| 249 | +void main() { |
| 250 | + vec4 col = texture(mainTexture, uv); |
| 251 | +
|
| 252 | + finalTex = vec4(pow(col.rgb, 2.2), col.a); |
| 253 | +} |
| 254 | +``` |
| 255 | + |
| 256 | +Computes and buffers will be covered in a separate page. Unlike with composites, they differ greatly in functionality, with buffers being significantly more powerful. |
0 commit comments