Watch
2
0
Fork
You've already forked raylib-cs
0

WASM examples (+ backports of new official examples) (#344)

* chg: New build system that uses the officially distributed binaries, bumped version to 8.0.0, simplified git workflow, removed deprecated OpenGL 1.1 functionality.

* chg: Modernize CI workflow, enable SourceLink

- Bump workflow actions to latest majors (Node 24); drop deprecated softprops/action-gh-release@v1
- Trigger push builds on main instead of master
- Create local nuget feed dir before pack (fixes NU1301)
- Enable Microsoft.SourceLink.GitHub for debugging symbols (ref PR #340)

* fix: centralized version data in Directory.build.props, and fixed various interop details that had incorrect function signatures

* chore: updated readme

* fix: version the native extract marker and chain download via DependsOnTargets

The .extracted marker now includes the raylib package name, so bumping
TargetRaylibTag re-extracts the new archive instead of silently keeping
(and packing/copying) the previous version's files.

_PrepareNativeLibrary and _StageWasmNative now depend directly on
_DownloadAndExtractInternal instead of CallTarget-ing it; dependency
targets run in the same project instance, so the resolved properties
(RaylibPackageName etc.) propagate naturally.

* fix: let the binding build for browser-wasm on both net8.0 and net10.0

The net8-era wasm workload (Microsoft.NET.Runtime.WebAssembly.Sdk 8.0.x,
auto-imported for RID browser-wasm) treats every browser-wasm project as
a wasm app: it forces OutputType=Exe after project evaluation (CS5001
for a classlib) and hooks its app-bundle build after Build, which errors
because a library has no assemblies to bundle. Opt Raylib-cs out via
DisableAutoWasmBuildApp (props time, before the workload defaults its
trigger) and pin OutputType back to Library in Directory.Build.targets
(evaluated after the workload props, so the assignment wins). net10's
wasm SDK needs neither workaround.

* chore: readme updated

* chg: simplifying build logic - a simple line in the documentation should save us the code here

* fix: Wrong signature of FrameBufferComplete

* chore: readme update

* feat: samples default to local project reference, and can optionally use the nuget package

* feat: backporting existing raylib-cs examples and new official raylib examples to WASM, adopting raylib's original code style

* chore: readme, gitignore, and targets backport.

* fix: Examples.csproj runs the download task when building locally

* feat: backporting existing raylib-cs examples and new official raylib examples to WASM, adopting raylib's original code style

* chore: readme, gitignore, and targets backport.

* chore: clean up linter warnings

* feat: html harness focuses the example and allows quick navigation with J/K instead.

* chore: readme mentions the property to use nuget vs. the local project reference

* feat: replaced the J/K navigation with good old HTML buttons

* chore: run dotnet format scoped default (was previously scoped to just 'style')
This commit is contained in:
tiger tiger tiger 2026-07-30 19:34:34 +02:00 committed by GitHub
commit 8c22e68c2a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
236 changed files with 40405 additions and 10896 deletions

View file

@ -0,0 +1,434 @@
/*******************************************************************************************
*
* raylib [shaders] example - deferred rendering
*
* Example complexity rating: [] 4/4
*
* NOTE: This example requires raylib OpenGL 3.3 or OpenGL ES 3.0
*
* Example originally created with raylib 4.5, last time updated with raylib 4.5
*
* Example contributed by Justin Andreas Lacoste (@27justin) and reviewed by Ramon Santamaria (@raysan5)
*
* Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
* BSD-like license that allows static linking with closed source software
*
* Copyright (c) 2023-2025 Justin Andreas Lacoste (@27justin)
*
********************************************************************************************/
using Examples.Shared;
namespace Examples.Shaders;
[ExcludeFromBrowser("multiple-render-target G-buffer, unsupported on WebGL1/GLSL100")]
public partial class DeferredRendering : IExample
{
private const int screenWidth = 800;
private const int screenHeight = 450;
#if BROWSER
const int GlslVersion = 100; // WebGL1 needs GLSL ES 100
#else
private const int GlslVersion = 330;
#endif
private const int MaxCubes = 30;
private const int MaxLights = 4;
private const float CubeScale = 0.25f;
// GL_READ_FRAMEBUFFER / GL_DRAW_FRAMEBUFFER / GL_DEPTH_BUFFER_BIT
private const uint RlReadFramebuffer = 0x8CA8;
private const uint RlDrawFramebuffer = 0x8CA9;
private const int GlDepthBufferBit = 0x00000100;
public string Name => "Shaders / Deferred Rendering";
public string Title => "raylib [shaders] example - deferred rendering";
// GBuffer data
private struct GBuffer
{
public uint FramebufferId;
public uint PositionTextureId;
public uint NormalTextureId;
public uint AlbedoSpecTextureId;
public uint DepthRenderbufferId;
}
// Deferred mode passes
private enum DeferredMode
{
Position,
Normal,
Albedo,
Shading
}
private Camera3D camera;
private Model model;
private Model cube;
private Shader gbufferShader;
private Shader deferredShader;
private GBuffer gBuffer;
private Light[] lights;
private Vector3[] cubePositions;
private float[] cubeRotations;
private DeferredMode mode;
// Texture units our g-buffer textures are bound to
private const int TexUnitPosition = 0;
private const int TexUnitNormal = 1;
private const int TexUnitAlbedoSpec = 2;
public unsafe void Init()
{
camera = new();
camera.Position = new Vector3(5.0f, 4.0f, 5.0f); // Camera position
camera.Target = new Vector3(0.0f, 1.0f, 0.0f); // Camera looking at point
camera.Up = new Vector3(0.0f, 1.0f, 0.0f); // Camera up vector (rotation towards target)
camera.FovY = 60.0f; // Camera field-of-view Y
camera.Projection = CameraProjection.Perspective; // Camera projection type
// Load plane model from a generated mesh
model = LoadModelFromMesh(GenMeshPlane(10.0f, 10.0f, 3, 3));
cube = LoadModelFromMesh(GenMeshCube(2.0f, 2.0f, 2.0f));
// Load geometry buffer (G-buffer) shader and deferred shader
gbufferShader = LoadShader(
$"resources/shaders/glsl{GlslVersion}/gbuffer.vs",
$"resources/shaders/glsl{GlslVersion}/gbuffer.fs"
);
deferredShader = LoadShader(
$"resources/shaders/glsl{GlslVersion}/deferred_shading.vs",
$"resources/shaders/glsl{GlslVersion}/deferred_shading.fs"
);
deferredShader.Locs[(int)ShaderLocationIndex.VectorView] = GetShaderLocation(deferredShader, "viewPosition");
// Initialize the G-buffer
gBuffer = new();
gBuffer.FramebufferId = Rlgl.LoadFramebuffer();
if (gBuffer.FramebufferId == 0)
{
TraceLog(TraceLogLevel.Warning, "Failed to create framebufferId");
}
Rlgl.EnableFramebuffer(gBuffer.FramebufferId);
// NOTE: Vertex positions are stored in a texture for simplicity. A better approach would use a depth texture
// (instead of a detph renderbuffer) to reconstruct world positions in the final render shader via clip-space position,
// depth, and the inverse view/projection matrices
// 16-bit precision ensures OpenGL ES 3 compatibility, though it may lack precision for real scenarios
gBuffer.PositionTextureId = Rlgl.LoadTexture(null, screenWidth, screenHeight, PixelFormat.UncompressedR16G16B16, 1);
// Similarly, 16-bit precision is used for normals ensures OpenGL ES 3 compatibility
gBuffer.NormalTextureId = Rlgl.LoadTexture(null, screenWidth, screenHeight, PixelFormat.UncompressedR16G16B16, 1);
// Albedo (diffuse color) and specular strength can be combined into one texture
// The color in RGB, and the specular strength in the alpha channel
gBuffer.AlbedoSpecTextureId = Rlgl.LoadTexture(null, screenWidth, screenHeight, PixelFormat.UncompressedR8G8B8A8, 1);
// Activate the draw buffers for our framebufferId
Rlgl.ActiveDrawBuffers(3);
// Now we attach our textures to the framebufferId
Rlgl.FramebufferAttach(gBuffer.FramebufferId, gBuffer.PositionTextureId, FramebufferAttachType.ColorChannel0, FramebufferAttachTextureType.Texture2D, 0);
Rlgl.FramebufferAttach(gBuffer.FramebufferId, gBuffer.NormalTextureId, FramebufferAttachType.ColorChannel1, FramebufferAttachTextureType.Texture2D, 0);
Rlgl.FramebufferAttach(gBuffer.FramebufferId, gBuffer.AlbedoSpecTextureId, FramebufferAttachType.ColorChannel2, FramebufferAttachTextureType.Texture2D, 0);
// Finally we attach the depth buffer
gBuffer.DepthRenderbufferId = Rlgl.LoadTextureDepth(screenWidth, screenHeight, true);
Rlgl.FramebufferAttach(gBuffer.FramebufferId, gBuffer.DepthRenderbufferId, FramebufferAttachType.Depth, FramebufferAttachTextureType.Renderbuffer, 0);
// Make sure our framebufferId is complete
// NOTE: rlFramebufferComplete() automatically unbinds the framebufferId, so we don't have to rlDisableFramebuffer() here
if (Rlgl.FramebufferComplete(gBuffer.FramebufferId) == 0)
{
TraceLog(TraceLogLevel.Warning, "Framebuffer is not complete");
}
// Now we initialize the sampler2D uniform's in the deferred shader
// We do this by setting the uniform's values to the texture units that
// we later bind our g-buffer textures to
Rlgl.EnableShader(deferredShader.Id);
int texUnitPosition = TexUnitPosition;
int texUnitNormal = TexUnitNormal;
int texUnitAlbedoSpec = TexUnitAlbedoSpec;
Raylib.SetShaderValue(deferredShader, GetShaderLocation(deferredShader, "gPosition"), texUnitPosition, ShaderUniformDataType.Sampler2D);
Raylib.SetShaderValue(deferredShader, GetShaderLocation(deferredShader, "gNormal"), texUnitNormal, ShaderUniformDataType.Sampler2D);
Raylib.SetShaderValue(deferredShader, GetShaderLocation(deferredShader, "gAlbedoSpec"), texUnitAlbedoSpec, ShaderUniformDataType.Sampler2D);
Rlgl.DisableShader();
// Assign out lighting shader to model
model.Materials[0].Shader = gbufferShader;
cube.Materials[0].Shader = gbufferShader;
// Create lights
lights = new Light[MaxLights];
lights[0] = Rlights.CreateLight(0, LightType.Point, new Vector3(-2, 1, -2), Vector3.Zero, Color.Yellow, deferredShader);
lights[1] = Rlights.CreateLight(1, LightType.Point, new Vector3(2, 1, 2), Vector3.Zero, Color.Red, deferredShader);
lights[2] = Rlights.CreateLight(2, LightType.Point, new Vector3(-2, 1, 2), Vector3.Zero, Color.Green, deferredShader);
lights[3] = Rlights.CreateLight(3, LightType.Point, new Vector3(2, 1, -2), Vector3.Zero, Color.Blue, deferredShader);
var rand = new Random();
cubePositions = new Vector3[MaxCubes];
cubeRotations = new float[MaxCubes];
for (var i = 0; i < MaxCubes; i++)
{
cubePositions[i] = new Vector3(
(float)(rand.Next() % 10) - 5,
(float)(rand.Next() % 5),
(float)(rand.Next() % 10) - 5
);
cubeRotations[i] = (float)(rand.Next() % 360);
}
mode = DeferredMode.Shading;
Rlgl.EnableDepthTest();
}
public unsafe void Update()
{
// Update
//----------------------------------------------------------------------------------
UpdateCamera(ref camera, CameraMode.Orbital);
// Update the shader with the camera view vector (points towards { 0.0f, 0.0f, 0.0f })
Raylib.SetShaderValue(
deferredShader,
deferredShader.Locs[(int)ShaderLocationIndex.VectorView],
camera.Position,
ShaderUniformDataType.Vec3
);
// Check key inputs to enable/disable lights
if (IsKeyPressed(KeyboardKey.Y))
{
lights[0].Enabled = !lights[0].Enabled;
}
if (IsKeyPressed(KeyboardKey.R))
{
lights[1].Enabled = !lights[1].Enabled;
}
if (IsKeyPressed(KeyboardKey.G))
{
lights[2].Enabled = !lights[2].Enabled;
}
if (IsKeyPressed(KeyboardKey.B))
{
lights[3].Enabled = !lights[3].Enabled;
}
// Check key inputs to switch between G-buffer textures
if (IsKeyPressed(KeyboardKey.One))
{
mode = DeferredMode.Position;
}
if (IsKeyPressed(KeyboardKey.Two))
{
mode = DeferredMode.Normal;
}
if (IsKeyPressed(KeyboardKey.Three))
{
mode = DeferredMode.Albedo;
}
if (IsKeyPressed(KeyboardKey.Four))
{
mode = DeferredMode.Shading;
}
// Update light values (actually, only enable/disable them)
for (var i = 0; i < MaxLights; i++)
{
Rlights.UpdateLightValues(deferredShader, lights[i]);
}
//----------------------------------------------------------------------------------
// Draw
//----------------------------------------------------------------------------------
BeginDrawing();
// Draw to the geometry buffer by first activating it
Rlgl.EnableFramebuffer(gBuffer.FramebufferId);
Rlgl.ClearColor(0, 0, 0, 0);
Rlgl.ClearScreenBuffers(); // Clear color and depth buffer
Rlgl.DisableColorBlend();
BeginMode3D(camera);
// NOTE: We have to use rlEnableShader here. `BeginShaderMode` or thus `rlSetShader`
// will not work, as they won't immediately load the shader program
Rlgl.EnableShader(gbufferShader.Id);
// When drawing a model here, make sure that the material's shaders are set to the gbuffer shader!
DrawModel(model, Vector3.Zero, 1.0f, Color.White);
DrawModel(cube, new Vector3(0.0f, 1.0f, 0.0f), 1.0f, Color.White);
for (var i = 0; i < MaxCubes; i++)
{
var position = cubePositions[i];
DrawModelEx(cube, position, new Vector3(1, 1, 1), cubeRotations[i], new Vector3(CubeScale, CubeScale, CubeScale), Color.White);
}
Rlgl.DisableShader();
EndMode3D();
Rlgl.EnableColorBlend();
// Go back to the default framebufferId (0) and draw our deferred shading
Rlgl.DisableFramebuffer();
Rlgl.ClearScreenBuffers(); // Clear color & depth buffer
switch (mode)
{
case DeferredMode.Shading:
{
BeginMode3D(camera);
Rlgl.DisableColorBlend();
Rlgl.EnableShader(deferredShader.Id);
// Bind our g-buffer textures
// We are binding them to locations that we earlier set in sampler2D uniforms `gPosition`, `gNormal`,
// and `gAlbedoSpec`
Rlgl.ActiveTextureSlot(TexUnitPosition);
Rlgl.EnableTexture(gBuffer.PositionTextureId);
Rlgl.ActiveTextureSlot(TexUnitNormal);
Rlgl.EnableTexture(gBuffer.NormalTextureId);
Rlgl.ActiveTextureSlot(TexUnitAlbedoSpec);
Rlgl.EnableTexture(gBuffer.AlbedoSpecTextureId);
// Finally, we draw a fullscreen quad to our default framebufferId
// This will now be shaded using our deferred shader
Rlgl.LoadDrawQuad();
Rlgl.DisableShader();
Rlgl.EnableColorBlend();
EndMode3D();
// As a last step, we now copy over the depth buffer from our g-buffer to the default framebufferId
Rlgl.BindFramebuffer(RlReadFramebuffer, gBuffer.FramebufferId);
Rlgl.BindFramebuffer(RlDrawFramebuffer, 0);
Rlgl.BlitFramebuffer(0, 0, screenWidth, screenHeight, 0, 0, screenWidth, screenHeight, GlDepthBufferBit);
Rlgl.DisableFramebuffer();
// Since our shader is now done and disabled, we can draw spheres
// that represent light positions in default forward rendering
BeginMode3D(camera);
Rlgl.EnableShader(Rlgl.GetShaderIdDefault());
for (var i = 0; i < MaxLights; i++)
{
if (lights[i].Enabled)
{
DrawSphereEx(lights[i].Position, 0.2f, 8, 8, lights[i].Color);
}
else
{
DrawSphereWires(lights[i].Position, 0.2f, 8, 8, ColorAlpha(lights[i].Color, 0.3f));
}
}
Rlgl.DisableShader();
EndMode3D();
DrawText("FINAL RESULT", 10, screenHeight - 30, 20, Color.DarkGreen);
}
break;
case DeferredMode.Position:
{
DrawTextureRec(
new Texture2D { Id = gBuffer.PositionTextureId, Width = screenWidth, Height = screenHeight },
new Rectangle(0, 0, screenWidth, -screenHeight),
Vector2.Zero,
Color.RayWhite
);
DrawText("POSITION TEXTURE", 10, screenHeight - 30, 20, Color.DarkGreen);
}
break;
case DeferredMode.Normal:
{
DrawTextureRec(
new Texture2D { Id = gBuffer.NormalTextureId, Width = screenWidth, Height = screenHeight },
new Rectangle(0, 0, screenWidth, -screenHeight),
Vector2.Zero,
Color.RayWhite
);
DrawText("NORMAL TEXTURE", 10, screenHeight - 30, 20, Color.DarkGreen);
}
break;
case DeferredMode.Albedo:
{
DrawTextureRec(
new Texture2D { Id = gBuffer.AlbedoSpecTextureId, Width = screenWidth, Height = screenHeight },
new Rectangle(0, 0, screenWidth, -screenHeight),
Vector2.Zero,
Color.RayWhite
);
DrawText("ALBEDO TEXTURE", 10, screenHeight - 30, 20, Color.DarkGreen);
}
break;
default:
break;
}
DrawText("Toggle lights keys: [Y][R][G][B]", 10, 40, 20, Color.DarkGray);
DrawText("Switch G-buffer textures: [1][2][3][4]", 10, 70, 20, Color.DarkGray);
DrawFPS(10, 10);
EndDrawing();
//----------------------------------------------------------------------------------
}
public void Unload()
{
// Unload the models
UnloadModel(model);
UnloadModel(cube);
// Unload shaders
UnloadShader(deferredShader);
UnloadShader(gbufferShader);
// Unload geometry buffer and all attached textures
Rlgl.UnloadFramebuffer(gBuffer.FramebufferId);
Rlgl.UnloadTexture(gBuffer.PositionTextureId);
Rlgl.UnloadTexture(gBuffer.NormalTextureId);
Rlgl.UnloadTexture(gBuffer.AlbedoSpecTextureId);
Rlgl.UnloadTexture(gBuffer.DepthRenderbufferId);
}
public static int Main()
{
// Initialization
//--------------------------------------------------------------------------------------
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - deferred rendering");
SetTargetFPS(60); // Set our game to run at 60 frames-per-second
//--------------------------------------------------------------------------------------
var game = new DeferredRendering();
game.Init();
// Main game loop
while (!WindowShouldClose()) // Detect window close button or ESC key
{
game.Update();
}
game.Unload();
// De-Initialization
//--------------------------------------------------------------------------------------
CloseWindow(); // Close window and OpenGL context
//--------------------------------------------------------------------------------------
return 0;
}
}