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:
parent
b207e633ae
commit
8c22e68c2a
236 changed files with 40405 additions and 10896 deletions
314
Examples/Shaders/ShadowmapRendering.cs
Normal file
314
Examples/Shaders/ShadowmapRendering.cs
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
/*******************************************************************************************
|
||||
*
|
||||
* raylib [shaders] example - shadowmap rendering
|
||||
*
|
||||
* Example complexity rating: [★★★★] 4/4
|
||||
*
|
||||
* Example originally created with raylib 5.0, last time updated with raylib 5.0
|
||||
*
|
||||
* Example contributed by TheManTheMythTheGameDev (@TheManTheMythTheGameDev) 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 TheManTheMythTheGameDev (@TheManTheMythTheGameDev)
|
||||
*
|
||||
********************************************************************************************/
|
||||
|
||||
using static Raylib_cs.Raymath;
|
||||
using static Raylib_cs.Rlgl;
|
||||
|
||||
namespace Examples.Shaders;
|
||||
|
||||
public partial class ShadowmapRendering : 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 ShadowmapResolution = 1024;
|
||||
|
||||
public string Name => "Shaders / Shadowmap Rendering";
|
||||
|
||||
public string Title => "raylib [shaders] example - shadowmap rendering";
|
||||
|
||||
public ConfigFlags ConfigFlags => ConfigFlags.Msaa4xHint;
|
||||
|
||||
private Camera3D camera;
|
||||
private Shader shadowShader;
|
||||
private Vector3 lightDir;
|
||||
private int lightDirLoc;
|
||||
private int lightVPLoc;
|
||||
private int shadowMapLoc;
|
||||
private Model cube;
|
||||
private Model robot;
|
||||
private unsafe ModelAnimation* anims;
|
||||
private int animCount;
|
||||
private RenderTexture2D shadowMap;
|
||||
private Camera3D lightCamera;
|
||||
private int frameCounter;
|
||||
private int textureActiveSlot;
|
||||
|
||||
public unsafe void Init()
|
||||
{
|
||||
// Shadows are a HUGE topic, and this example shows an extremely simple implementation of the shadowmapping algorithm,
|
||||
// which is the industry standard for shadows. This algorithm can be extended in a ridiculous number of ways to improve
|
||||
// realism and also adapt it for different scenes. This is pretty much the simplest possible implementation
|
||||
|
||||
camera = new();
|
||||
camera.Position = new Vector3(10.0f, 10.0f, 10.0f);
|
||||
camera.Target = Vector3.Zero;
|
||||
camera.Projection = CameraProjection.Perspective;
|
||||
camera.Up = new Vector3(0.0f, 1.0f, 0.0f);
|
||||
camera.FovY = 45.0f;
|
||||
|
||||
shadowShader = LoadShader(
|
||||
$"resources/shaders/glsl{GlslVersion}/shadowmap.vs",
|
||||
$"resources/shaders/glsl{GlslVersion}/shadowmap.fs"
|
||||
);
|
||||
shadowShader.Locs[(int)ShaderLocationIndex.VectorView] = GetShaderLocation(shadowShader, "viewPos");
|
||||
|
||||
lightDir = Vector3Normalize(new Vector3(0.35f, -1.0f, -0.35f));
|
||||
var lightColor = Color.White;
|
||||
var lightColorNormalized = ColorNormalize(lightColor);
|
||||
lightDirLoc = GetShaderLocation(shadowShader, "lightDir");
|
||||
var lightColLoc = GetShaderLocation(shadowShader, "lightColor");
|
||||
Raylib.SetShaderValue(shadowShader, lightDirLoc, lightDir, ShaderUniformDataType.Vec3);
|
||||
Raylib.SetShaderValue(shadowShader, lightColLoc, lightColorNormalized, ShaderUniformDataType.Vec4);
|
||||
var ambientLoc = GetShaderLocation(shadowShader, "ambient");
|
||||
var ambient = new[] { 0.1f, 0.1f, 0.1f, 1.0f };
|
||||
Raylib.SetShaderValue(shadowShader, ambientLoc, ambient, ShaderUniformDataType.Vec4);
|
||||
lightVPLoc = GetShaderLocation(shadowShader, "lightVP");
|
||||
shadowMapLoc = GetShaderLocation(shadowShader, "shadowMap");
|
||||
var shadowMapResolution = ShadowmapResolution;
|
||||
Raylib.SetShaderValue(shadowShader, GetShaderLocation(shadowShader, "shadowMapResolution"), shadowMapResolution, ShaderUniformDataType.Int);
|
||||
|
||||
cube = LoadModelFromMesh(GenMeshCube(1.0f, 1.0f, 1.0f));
|
||||
cube.Materials[0].Shader = shadowShader;
|
||||
robot = LoadModel("resources/models/robot.glb");
|
||||
for (var i = 0; i < robot.MaterialCount; i++)
|
||||
{
|
||||
robot.Materials[i].Shader = shadowShader;
|
||||
}
|
||||
|
||||
animCount = 0;
|
||||
anims = LoadModelAnimations("resources/models/robot.glb", ref animCount);
|
||||
|
||||
shadowMap = LoadShadowmapRenderTexture(ShadowmapResolution, ShadowmapResolution);
|
||||
|
||||
// For the shadowmapping algorithm, we will be rendering everything from the light's point of view
|
||||
lightCamera = new();
|
||||
lightCamera.Position = Vector3Scale(lightDir, -15.0f);
|
||||
lightCamera.Target = Vector3.Zero;
|
||||
lightCamera.Projection = CameraProjection.Orthographic; // Use an orthographic projection for directional lights
|
||||
lightCamera.Up = new Vector3(0.0f, 1.0f, 0.0f);
|
||||
lightCamera.FovY = 20.0f;
|
||||
|
||||
frameCounter = 0;
|
||||
textureActiveSlot = 10; // Can be anything 0 to 15, but 0 will probably be taken up
|
||||
}
|
||||
|
||||
public unsafe void Update()
|
||||
{
|
||||
// Update
|
||||
//----------------------------------------------------------------------------------
|
||||
var deltaTime = GetFrameTime();
|
||||
|
||||
var cameraPos = camera.Position;
|
||||
Raylib.SetShaderValue(shadowShader, shadowShader.Locs[(int)ShaderLocationIndex.VectorView], cameraPos, ShaderUniformDataType.Vec3);
|
||||
UpdateCamera(ref camera, CameraMode.Orbital);
|
||||
|
||||
frameCounter++;
|
||||
frameCounter %= anims[0].KeyFrameCount;
|
||||
UpdateModelAnimation(robot, anims[0], (float)frameCounter);
|
||||
|
||||
// Move light with arrow keys
|
||||
const float cameraSpeed = 0.05f;
|
||||
if (IsKeyDown(KeyboardKey.Left))
|
||||
{
|
||||
if (lightDir.X < 0.6f)
|
||||
{
|
||||
lightDir.X += cameraSpeed * 60.0f * deltaTime;
|
||||
}
|
||||
}
|
||||
if (IsKeyDown(KeyboardKey.Right))
|
||||
{
|
||||
if (lightDir.X > -0.6f)
|
||||
{
|
||||
lightDir.X -= cameraSpeed * 60.0f * deltaTime;
|
||||
}
|
||||
}
|
||||
if (IsKeyDown(KeyboardKey.Up))
|
||||
{
|
||||
if (lightDir.Z < 0.6f)
|
||||
{
|
||||
lightDir.Z += cameraSpeed * 60.0f * deltaTime;
|
||||
}
|
||||
}
|
||||
if (IsKeyDown(KeyboardKey.Down))
|
||||
{
|
||||
if (lightDir.Z > -0.6f)
|
||||
{
|
||||
lightDir.Z -= cameraSpeed * 60.0f * deltaTime;
|
||||
}
|
||||
}
|
||||
|
||||
lightDir = Vector3Normalize(lightDir);
|
||||
lightCamera.Position = Vector3Scale(lightDir, -15.0f);
|
||||
Raylib.SetShaderValue(shadowShader, lightDirLoc, lightDir, ShaderUniformDataType.Vec3);
|
||||
//----------------------------------------------------------------------------------
|
||||
|
||||
// Draw
|
||||
//----------------------------------------------------------------------------------
|
||||
// PASS 01: Render all objects into the shadowmap render texture
|
||||
// We record all the objects' depths (as rendered from the light source's point of view) in a buffer
|
||||
// Anything that is "visible" to the light is in light, anything that isn't is in shadow
|
||||
// We can later use the depth buffer when rendering everything from the player's point of view
|
||||
// to determine whether a given point is "visible" to the light
|
||||
Matrix4x4 lightView;
|
||||
Matrix4x4 lightProj;
|
||||
BeginTextureMode(shadowMap);
|
||||
ClearBackground(Color.White);
|
||||
|
||||
BeginMode3D(lightCamera);
|
||||
lightView = GetMatrixModelview();
|
||||
lightProj = GetMatrixProjection();
|
||||
DrawScene(cube, robot);
|
||||
EndMode3D();
|
||||
|
||||
EndTextureMode();
|
||||
var lightViewProj = MatrixMultiply(lightView, lightProj);
|
||||
|
||||
// PASS 02: Draw the scene into main framebuffer, using the generated shadowmap
|
||||
BeginDrawing();
|
||||
ClearBackground(Color.RayWhite);
|
||||
|
||||
SetShaderValueMatrix(shadowShader, lightVPLoc, lightViewProj);
|
||||
EnableShader(shadowShader.Id);
|
||||
|
||||
ActiveTextureSlot(textureActiveSlot);
|
||||
EnableTexture(shadowMap.Depth.Id);
|
||||
var slot = textureActiveSlot;
|
||||
SetUniform(shadowMapLoc, &slot, (int)ShaderUniformDataType.Int, 1);
|
||||
|
||||
BeginMode3D(camera);
|
||||
DrawScene(cube, robot); // Draw the same exact things as we drew in the shadowmap!
|
||||
EndMode3D();
|
||||
|
||||
DrawText("Use the arrow keys to rotate the light!", 10, 10, 30, Color.Red);
|
||||
DrawText("Shadows in raylib using the shadowmapping algorithm!", screenWidth - 280, screenHeight - 20, 10, Color.Gray);
|
||||
|
||||
EndDrawing();
|
||||
|
||||
if (IsKeyPressed(KeyboardKey.F))
|
||||
{
|
||||
TakeScreenshot("shaders_shadowmap.png");
|
||||
}
|
||||
//----------------------------------------------------------------------------------
|
||||
}
|
||||
|
||||
public unsafe void Unload()
|
||||
{
|
||||
UnloadShader(shadowShader);
|
||||
UnloadModel(cube);
|
||||
UnloadModel(robot);
|
||||
UnloadModelAnimations(anims, animCount);
|
||||
UnloadShadowmapRenderTexture(shadowMap);
|
||||
}
|
||||
|
||||
// Load render texture for shadowmap projection
|
||||
// NOTE: Load framebuffer with only a texture depth attachment,
|
||||
// no color attachment required for shadowmap
|
||||
private static unsafe RenderTexture2D LoadShadowmapRenderTexture(int width, int height)
|
||||
{
|
||||
RenderTexture2D target = new();
|
||||
|
||||
target.Id = LoadFramebuffer(); // Load an empty framebuffer
|
||||
target.Texture.Width = width;
|
||||
target.Texture.Height = height;
|
||||
|
||||
if (target.Id > 0)
|
||||
{
|
||||
EnableFramebuffer(target.Id);
|
||||
|
||||
// Create depth texture
|
||||
// NOTE: No need a color texture attachment for the shadowmap
|
||||
target.Depth.Id = LoadTextureDepth(width, height, false);
|
||||
target.Depth.Width = width;
|
||||
target.Depth.Height = height;
|
||||
target.Depth.Format = (PixelFormat)19; // DEPTH_COMPONENT_24BIT?
|
||||
target.Depth.Mipmaps = 1;
|
||||
|
||||
// Attach depth texture to FBO
|
||||
FramebufferAttach(target.Id, target.Depth.Id, FramebufferAttachType.Depth, FramebufferAttachTextureType.Texture2D, 0);
|
||||
|
||||
// Check if fbo is complete with attachments (valid)
|
||||
if (FramebufferComplete(target.Id) != 0)
|
||||
{
|
||||
TraceLog(TraceLogLevel.Info, $"FBO: [ID {target.Id}] Framebuffer object created successfully");
|
||||
}
|
||||
|
||||
DisableFramebuffer();
|
||||
}
|
||||
else
|
||||
{
|
||||
TraceLog(TraceLogLevel.Warning, "FBO: Framebuffer object can not be created");
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
// Unload shadowmap render texture from GPU memory (VRAM)
|
||||
private static void UnloadShadowmapRenderTexture(RenderTexture2D target)
|
||||
{
|
||||
if (target.Id > 0)
|
||||
{
|
||||
// NOTE: Depth texture/renderbuffer is automatically
|
||||
// queried and deleted before deleting framebuffer
|
||||
UnloadFramebuffer(target.Id);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw full scene projecting shadows
|
||||
// NOTE: Required to be called several time to generate shadowmap
|
||||
private static void DrawScene(Model cube, Model robot)
|
||||
{
|
||||
DrawModelEx(cube, Vector3.Zero, new Vector3(0.0f, 1.0f, 0.0f), 0.0f, new Vector3(10.0f, 1.0f, 10.0f), Color.Blue);
|
||||
DrawModelEx(cube, new Vector3(1.5f, 1.0f, -1.5f), new Vector3(0.0f, 1.0f, 0.0f), 0.0f, Vector3.One, Color.White);
|
||||
DrawModelEx(robot, new Vector3(0.0f, 0.5f, 0.0f), new Vector3(0.0f, 1.0f, 0.0f), 0.0f, new Vector3(1.0f, 1.0f, 1.0f), Color.Red);
|
||||
}
|
||||
|
||||
public static int Main()
|
||||
{
|
||||
// Initialization
|
||||
//--------------------------------------------------------------------------------------
|
||||
SetConfigFlags(ConfigFlags.Msaa4xHint);
|
||||
InitWindow(screenWidth, screenHeight, "raylib [shaders] example - shadowmap rendering");
|
||||
|
||||
SetTargetFPS(60);
|
||||
//--------------------------------------------------------------------------------------
|
||||
|
||||
var game = new ShadowmapRendering();
|
||||
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;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue