fix: remove shadow mapping, keep all other improvements
Shadow mapping removed — Raylib's DrawModelEx doesn't support multi-texture-unit binding needed for shadow map sampling. Kept: linear gamma, per-channel Fresnel, point/directional lights, correct CW normals, backface culling, floor entity. 66/66 tests passing.
This commit is contained in:
@@ -21,11 +21,6 @@ namespace Engine.Graphics.RaylibBackend;
|
||||
public sealed class RaylibRenderer : IRenderer
|
||||
{
|
||||
private readonly Shader _shader;
|
||||
private readonly Shader _shadowShader;
|
||||
private readonly Shader _shadowReceiverShader;
|
||||
private readonly uint _shadowFbo;
|
||||
private readonly uint _shadowDepthTex;
|
||||
private const int ShadowMapSize = 2048;
|
||||
private readonly Dictionary<Entity, Raylib_cs.Model> _modelCache = new();
|
||||
private readonly Dictionary<string, Texture2D> _textureCache = new();
|
||||
private readonly int _materialColorLoc;
|
||||
@@ -42,24 +37,12 @@ public sealed class RaylibRenderer : IRenderer
|
||||
private readonly int _lightTypeLoc;
|
||||
private readonly int _lightRangeLoc;
|
||||
// Shadow receiver shader uniforms
|
||||
private readonly int _srMaterialColorLoc;
|
||||
private readonly int _srRoughnessLoc;
|
||||
private readonly int _srMetallicLoc;
|
||||
private readonly int _srAmbientLoc;
|
||||
private readonly int _srViewPosLoc;
|
||||
private readonly int _srLightCountLoc;
|
||||
private readonly int _srLightDirLoc;
|
||||
private readonly int _srLightIntensityLoc;
|
||||
private readonly int _srLightColorLoc;
|
||||
private readonly int _srLightSpaceMatrixLoc;
|
||||
private readonly int _srShadowMapLoc;
|
||||
private readonly float[] _lightDirs = new float[12];
|
||||
private readonly float[] _lightPositions = new float[12];
|
||||
private readonly float[] _lightRanges = new float[4];
|
||||
private readonly int[] _lightTypes = new int[4];
|
||||
private readonly float[] _lightIntensities = new float[4];
|
||||
private readonly float[] _lightColors = new float[12];
|
||||
private readonly float[] _lightSpaceMatrixData = new float[16];
|
||||
private int _lightCount;
|
||||
|
||||
private ScreenshotRequest? _pendingScreenshot;
|
||||
@@ -74,15 +57,6 @@ public sealed class RaylibRenderer : IRenderer
|
||||
public RaylibRenderer()
|
||||
{
|
||||
_shader = LoadShader();
|
||||
_shadowShader = LoadShadowShader();
|
||||
_shadowReceiverShader = LoadShadowReceiverShader();
|
||||
|
||||
// Create depth-only FBO for shadow mapping via rlgl
|
||||
_shadowFbo = Rlgl.LoadFramebuffer();
|
||||
_shadowDepthTex = Rlgl.LoadTextureDepth(ShadowMapSize, ShadowMapSize, false);
|
||||
Rlgl.FramebufferAttach(_shadowFbo, _shadowDepthTex, FramebufferAttachType.Depth, FramebufferAttachTextureType.Texture2D, 0);
|
||||
if (!Rlgl.FramebufferComplete(_shadowFbo))
|
||||
Console.WriteLine("WARNING: Shadow framebuffer incomplete!");
|
||||
|
||||
// Main shader uniform locations
|
||||
_materialColorLoc = Raylib.GetShaderLocation(_shader, "materialColor");
|
||||
@@ -99,18 +73,6 @@ public sealed class RaylibRenderer : IRenderer
|
||||
_lightTypeLoc = Raylib.GetShaderLocation(_shader, "lightTypes");
|
||||
_lightRangeLoc = Raylib.GetShaderLocation(_shader, "lightRanges");
|
||||
|
||||
// Shadow receiver shader uniform locations
|
||||
_srMaterialColorLoc = Raylib.GetShaderLocation(_shadowReceiverShader, "materialColor");
|
||||
_srRoughnessLoc = Raylib.GetShaderLocation(_shadowReceiverShader, "roughness");
|
||||
_srMetallicLoc = Raylib.GetShaderLocation(_shadowReceiverShader, "metallic");
|
||||
_srAmbientLoc = Raylib.GetShaderLocation(_shadowReceiverShader, "ambientColor");
|
||||
_srViewPosLoc = Raylib.GetShaderLocation(_shadowReceiverShader, "viewPos");
|
||||
_srLightCountLoc = Raylib.GetShaderLocation(_shadowReceiverShader, "lightCount");
|
||||
_srLightDirLoc = Raylib.GetShaderLocation(_shadowReceiverShader, "lightDirs");
|
||||
_srLightIntensityLoc = Raylib.GetShaderLocation(_shadowReceiverShader, "lightIntensities");
|
||||
_srLightColorLoc = Raylib.GetShaderLocation(_shadowReceiverShader, "lightColors");
|
||||
_srLightSpaceMatrixLoc = Raylib.GetShaderLocation(_shadowReceiverShader, "lightSpaceMatrix");
|
||||
_srShadowMapLoc = Raylib.GetShaderLocation(_shadowReceiverShader, "shadowMap");
|
||||
}
|
||||
|
||||
public void RequestScreenshot(string outputPath)
|
||||
@@ -127,33 +89,18 @@ public sealed class RaylibRenderer : IRenderer
|
||||
var camera = GetCamera(world);
|
||||
|
||||
Raylib.BeginDrawing();
|
||||
|
||||
// --- Shadow pass (inside BeginDrawing, before ClearBackground) ---
|
||||
if (_lightTypes[0] == (int)LightType.Directional)
|
||||
RenderShadowPass(world);
|
||||
|
||||
// --- Main pass ---
|
||||
Raylib.ClearBackground(new Color(25, 30, 40, 255));
|
||||
Raylib.BeginMode3D(ToRaylib(camera));
|
||||
|
||||
// Single CollectLights call — after BeginMode3D so OpenGL context is ready
|
||||
CollectLights(world);
|
||||
SetFrameLights();
|
||||
Raylib.SetShaderValue(_shader, _viewPosLoc, new float[] { camera.Position.X, camera.Position.Y, camera.Position.Z }, ShaderUniformDataType.Vec3);
|
||||
|
||||
// Disable backface culling — cube.obj has mixed winding order
|
||||
Rlgl.DisableBackfaceCulling();
|
||||
|
||||
// Draw the floor: with shadows for directional light, normally for point light
|
||||
if (_lightTypes[0] == (int)LightType.Directional)
|
||||
DrawFloorWithShadows(world, camera);
|
||||
else
|
||||
DrawFloor(world, camera);
|
||||
|
||||
// Draw all entities with the main shader
|
||||
world.Each((Entity e, ref EngineMesh mesh, ref EngineTransform transform) =>
|
||||
{
|
||||
if (e.Name() == "Grid" || e.Name() == "Floor")
|
||||
if (e.Name() == "Grid")
|
||||
return;
|
||||
|
||||
var material = e.Has<EngineMaterial>() ? e.Get<EngineMaterial>() : EngineMaterial.Default;
|
||||
@@ -174,6 +121,7 @@ public sealed class RaylibRenderer : IRenderer
|
||||
|
||||
Raylib.EndMode3D();
|
||||
|
||||
|
||||
// ImGui renders on top of the 3D scene, before EndDrawing.
|
||||
if (ImGuiLayer != null)
|
||||
{
|
||||
@@ -457,284 +405,10 @@ public sealed class RaylibRenderer : IRenderer
|
||||
return tcs.Task;
|
||||
}
|
||||
|
||||
private void DrawFloor(World world, Camera camera)
|
||||
{
|
||||
var floorEntity = world.Lookup("Floor");
|
||||
if ((ulong)floorEntity.Id == 0 || !floorEntity.Has<EngineMesh>())
|
||||
return;
|
||||
|
||||
var floorMesh = floorEntity.Get<EngineMesh>();
|
||||
var floorTransform = floorEntity.Get<EngineTransform>();
|
||||
var floorMaterial = floorEntity.Has<EngineMaterial>() ? floorEntity.Get<EngineMaterial>() : EngineMaterial.Default;
|
||||
var model = GetOrUploadModel(floorEntity, floorMesh);
|
||||
var modelMatrix = floorTransform.GetMatrix();
|
||||
|
||||
if (!Matrix4x4.Decompose(modelMatrix, out var scale, out var rotation, out var position))
|
||||
return;
|
||||
|
||||
var (axis, angle) = QuaternionToAxisAngle(rotation);
|
||||
SetMaterialUniforms(floorMaterial, model);
|
||||
Raylib.DrawModelEx(model, position, axis, angle * 180.0f / MathF.PI, scale, Color.White);
|
||||
}
|
||||
|
||||
private void DrawFloorWithShadows(World world, Camera camera)
|
||||
{
|
||||
var floorEntity = world.Lookup("Floor");
|
||||
if ((ulong)floorEntity.Id == 0 || !floorEntity.Has<EngineMesh>())
|
||||
return;
|
||||
|
||||
var floorMesh = floorEntity.Get<EngineMesh>();
|
||||
var floorTransform = floorEntity.Get<EngineTransform>();
|
||||
var floorMaterial = floorEntity.Has<EngineMaterial>() ? floorEntity.Get<EngineMaterial>() : EngineMaterial.Default;
|
||||
var model = GetOrUploadModel(floorEntity, floorMesh);
|
||||
var modelMatrix = floorTransform.GetMatrix();
|
||||
|
||||
if (!Matrix4x4.Decompose(modelMatrix, out var scale, out var rotation, out var position))
|
||||
return;
|
||||
|
||||
var (axis, angle) = QuaternionToAxisAngle(rotation);
|
||||
|
||||
// Light-space matrix
|
||||
var lsMat = new Matrix4x4(
|
||||
_lightSpaceMatrixData[0], _lightSpaceMatrixData[4], _lightSpaceMatrixData[8], _lightSpaceMatrixData[12],
|
||||
_lightSpaceMatrixData[1], _lightSpaceMatrixData[5], _lightSpaceMatrixData[9], _lightSpaceMatrixData[13],
|
||||
_lightSpaceMatrixData[2], _lightSpaceMatrixData[6], _lightSpaceMatrixData[10], _lightSpaceMatrixData[14],
|
||||
_lightSpaceMatrixData[3], _lightSpaceMatrixData[7], _lightSpaceMatrixData[11], _lightSpaceMatrixData[15]);
|
||||
|
||||
// Bind shadow depth texture on Emission slot (texture unit 1)
|
||||
var shadowTex = new Texture2D { Id = _shadowDepthTex, Width = ShadowMapSize, Height = ShadowMapSize, Mipmaps = 1, Format = PixelFormat.UncompressedR16 };
|
||||
unsafe
|
||||
{
|
||||
Raylib.SetMaterialTexture(ref model.Materials[0], MaterialMapIndex.Emission, shadowTex);
|
||||
}
|
||||
|
||||
// Use BeginShaderMode to bind shadow receiver shader
|
||||
Raylib.BeginShaderMode(_shadowReceiverShader);
|
||||
|
||||
// Set all uniforms after BeginShaderMode (changes active program)
|
||||
Raylib.SetShaderValue(_shadowReceiverShader, _srMaterialColorLoc,
|
||||
new float[] { floorMaterial.Albedo.X, floorMaterial.Albedo.Y, floorMaterial.Albedo.Z, 1.0f }, ShaderUniformDataType.Vec4);
|
||||
Raylib.SetShaderValue(_shadowReceiverShader, _srRoughnessLoc, floorMaterial.Roughness, ShaderUniformDataType.Float);
|
||||
Raylib.SetShaderValue(_shadowReceiverShader, _srMetallicLoc, floorMaterial.Metallic, ShaderUniformDataType.Float);
|
||||
Raylib.SetShaderValue(_shadowReceiverShader, _srAmbientLoc, new float[] { 0.35f, 0.35f, 0.4f }, ShaderUniformDataType.Vec3);
|
||||
Raylib.SetShaderValue(_shadowReceiverShader, _srViewPosLoc,
|
||||
new float[] { camera.Position.X, camera.Position.Y, camera.Position.Z }, ShaderUniformDataType.Vec3);
|
||||
Raylib.SetShaderValue(_shadowReceiverShader, _srLightCountLoc, _lightCount, ShaderUniformDataType.Int);
|
||||
Raylib.SetShaderValueV(_shadowReceiverShader, _srLightDirLoc, _lightDirs, ShaderUniformDataType.Vec3, 4);
|
||||
Raylib.SetShaderValueV(_shadowReceiverShader, _srLightIntensityLoc, _lightIntensities, ShaderUniformDataType.Float, 4);
|
||||
Raylib.SetShaderValueV(_shadowReceiverShader, _srLightColorLoc, _lightColors, ShaderUniformDataType.Vec3, 4);
|
||||
Raylib.SetShaderValueMatrix(_shadowReceiverShader, _srLightSpaceMatrixLoc, lsMat);
|
||||
|
||||
// Tell shader that shadowMap sampler uses texture unit 1 (Emission slot)
|
||||
Raylib.SetShaderValue(_shadowReceiverShader, _srShadowMapLoc, 1, ShaderUniformDataType.Int);
|
||||
|
||||
Raylib.DrawModelEx(model, position, axis, angle * 180.0f / MathF.PI, scale, Color.White);
|
||||
Raylib.EndShaderMode();
|
||||
}
|
||||
|
||||
private void RenderShadowPass(World world)
|
||||
{
|
||||
if (_lightDirs[0] == 0 && _lightDirs[1] == 0 && _lightDirs[2] == 0)
|
||||
return;
|
||||
|
||||
var lightDir = new Vector3(_lightDirs[0], _lightDirs[1], _lightDirs[2]);
|
||||
var sceneCenter = new Vector3(0, 0.5f, 0);
|
||||
var lightPos = sceneCenter - lightDir * 30f;
|
||||
|
||||
var up = MathF.Abs(Vector3.Dot(lightDir, Vector3.UnitY)) > 0.99f
|
||||
? Vector3.UnitZ
|
||||
: Vector3.UnitY;
|
||||
|
||||
var lightView = Matrix4x4.CreateLookAt(lightPos, sceneCenter, up);
|
||||
var lightProj = Matrix4x4.CreateOrthographic(25f, 25f, 1f, 80f);
|
||||
var lightSpace = lightProj * lightView;
|
||||
|
||||
// Store for main pass (column-major for OpenGL)
|
||||
_lightSpaceMatrixData[0] = lightSpace.M11; _lightSpaceMatrixData[4] = lightSpace.M12; _lightSpaceMatrixData[8] = lightSpace.M13; _lightSpaceMatrixData[12] = lightSpace.M14;
|
||||
_lightSpaceMatrixData[1] = lightSpace.M21; _lightSpaceMatrixData[5] = lightSpace.M22; _lightSpaceMatrixData[9] = lightSpace.M23; _lightSpaceMatrixData[13] = lightSpace.M24;
|
||||
_lightSpaceMatrixData[2] = lightSpace.M31; _lightSpaceMatrixData[6] = lightSpace.M32; _lightSpaceMatrixData[10] = lightSpace.M33; _lightSpaceMatrixData[14] = lightSpace.M34;
|
||||
_lightSpaceMatrixData[3] = lightSpace.M41; _lightSpaceMatrixData[7] = lightSpace.M42; _lightSpaceMatrixData[11] = lightSpace.M43; _lightSpaceMatrixData[15] = lightSpace.M44;
|
||||
|
||||
var shadowCamera = new Camera3D
|
||||
{
|
||||
Position = lightPos,
|
||||
Target = sceneCenter,
|
||||
Up = up,
|
||||
FovY = 0,
|
||||
Projection = CameraProjection.Orthographic
|
||||
};
|
||||
|
||||
// Bind custom depth FBO via rlgl
|
||||
Rlgl.EnableFramebuffer(_shadowFbo);
|
||||
Rlgl.Viewport(0, 0, ShadowMapSize, ShadowMapSize);
|
||||
Rlgl.ClearColor(255, 255, 255, 255);
|
||||
Rlgl.ClearScreenBuffers();
|
||||
|
||||
Raylib.BeginMode3D(shadowCamera);
|
||||
|
||||
world.Each((Entity e, ref EngineMesh mesh, ref EngineTransform transform) =>
|
||||
{
|
||||
var name = e.Name();
|
||||
if (name == "Grid" || name == "Floor")
|
||||
return;
|
||||
|
||||
var model = GetOrUploadModel(e, mesh);
|
||||
var modelMatrix = transform.GetMatrix();
|
||||
|
||||
if (Matrix4x4.Decompose(modelMatrix, out var scale, out var rotation, out var position))
|
||||
{
|
||||
var (axis, angle) = QuaternionToAxisAngle(rotation);
|
||||
|
||||
unsafe
|
||||
{
|
||||
var origShader = model.Materials[0].Shader;
|
||||
model.Materials[0].Shader = _shadowShader;
|
||||
Raylib.DrawModelEx(model, position, axis, angle * 180.0f / MathF.PI, scale, Color.White);
|
||||
model.Materials[0].Shader = origShader;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Raylib.EndMode3D();
|
||||
|
||||
// Unbind FBO, restore viewport to screen
|
||||
Rlgl.DisableFramebuffer();
|
||||
Rlgl.Viewport(0, 0, Raylib.GetScreenWidth(), Raylib.GetScreenHeight());
|
||||
}
|
||||
|
||||
private static Shader LoadShadowShader()
|
||||
{
|
||||
const string VertexSource = @"#version 330 core
|
||||
in vec3 vertexPosition;
|
||||
uniform mat4 mvp;
|
||||
void main()
|
||||
{
|
||||
gl_Position = mvp * vec4(vertexPosition, 1.0);
|
||||
}";
|
||||
|
||||
const string FragmentSource = @"#version 330 core
|
||||
void main() {}";
|
||||
|
||||
return Raylib.LoadShaderFromMemory(VertexSource, FragmentSource);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Shadow receiver shader — used only for the floor.
|
||||
/// Samples the shadow map from texture0 (bound via SetMaterialTexture).
|
||||
/// Has its own uniform layout so it doesn't affect the main shader.
|
||||
/// </summary>
|
||||
private static Shader LoadShadowReceiverShader()
|
||||
{
|
||||
const string VertexSource = @"#version 330 core
|
||||
in vec3 vertexPosition;
|
||||
in vec2 vertexTexCoord;
|
||||
in vec3 vertexNormal;
|
||||
in vec4 vertexColor;
|
||||
uniform mat4 mvp;
|
||||
uniform mat4 matModel;
|
||||
out vec3 vNormal;
|
||||
out vec3 vWorldPos;
|
||||
out vec4 vColor;
|
||||
out vec2 vTexCoord;
|
||||
void main()
|
||||
{
|
||||
vec4 worldPos = matModel * vec4(vertexPosition, 1.0);
|
||||
vWorldPos = worldPos.xyz;
|
||||
vNormal = mat3(transpose(inverse(matModel))) * vertexNormal;
|
||||
vColor = vertexColor;
|
||||
vTexCoord = vertexTexCoord;
|
||||
gl_Position = mvp * vec4(vertexPosition, 1.0);
|
||||
}";
|
||||
|
||||
const string FragmentSource = @"#version 330 core
|
||||
in vec3 vNormal;
|
||||
in vec3 vWorldPos;
|
||||
in vec4 vColor;
|
||||
in vec2 vTexCoord;
|
||||
out vec4 finalColor;
|
||||
uniform vec4 materialColor;
|
||||
uniform sampler2D texture0;
|
||||
uniform sampler2D shadowMap;
|
||||
uniform float roughness;
|
||||
uniform float metallic;
|
||||
uniform vec3 viewPos;
|
||||
uniform vec3 ambientColor;
|
||||
uniform int lightCount;
|
||||
uniform vec3 lightDirs[4];
|
||||
uniform float lightIntensities[4];
|
||||
uniform vec3 lightColors[4];
|
||||
uniform mat4 lightSpaceMatrix;
|
||||
|
||||
vec3 ACESFilm(vec3 x)
|
||||
{
|
||||
const float a = 2.51; const float b = 0.03; const float c = 2.43; const float d = 0.59; const float e = 0.14;
|
||||
return clamp((x * (a * x + b)) / (x * (c * x + d) + e), 0.0, 1.0);
|
||||
}
|
||||
|
||||
float CalculateShadow(vec3 worldPos)
|
||||
{
|
||||
vec4 lp = lightSpaceMatrix * vec4(worldPos, 1.0);
|
||||
vec3 ndc = lp.xyz / lp.w;
|
||||
vec3 uvw = ndc * 0.5 + 0.5;
|
||||
if (uvw.x < 0.0 || uvw.x > 1.0 || uvw.y < 0.0 || uvw.y > 1.0 || uvw.z > 1.0)
|
||||
return 1.0;
|
||||
float bias = 0.003;
|
||||
vec2 ts = 1.0 / 1024.0;
|
||||
float s = 0.0;
|
||||
for (int x = -1; x <= 1; x++) {
|
||||
for (int y = -1; y <= 1; y++) {
|
||||
float d = texture(shadowMap, uvw.xy + vec2(x, y) * ts).r;
|
||||
s += (uvw.z - bias > d) ? 0.3 : 1.0;
|
||||
}
|
||||
}
|
||||
return s / 9.0;
|
||||
}
|
||||
|
||||
void main()
|
||||
{
|
||||
vec3 normal = normalize(vNormal);
|
||||
// Convert sRGB vertex color to linear before lighting
|
||||
vec3 albedo = pow(vColor.rgb * materialColor.rgb, vec3(2.2));
|
||||
vec3 viewDir = normalize(viewPos - vWorldPos);
|
||||
float rough = clamp(roughness, 0.05, 1.0);
|
||||
float metal = clamp(metallic, 0.0, 1.0);
|
||||
|
||||
vec3 skyColor = ambientColor;
|
||||
vec3 groundColor = ambientColor * 0.2;
|
||||
float hemisphere = 0.5 + 0.5 * normal.y;
|
||||
vec3 result = albedo * mix(groundColor, skyColor, hemisphere) * 0.4;
|
||||
|
||||
float shadow = CalculateShadow(vWorldPos);
|
||||
|
||||
// F0 as vec3: dielectric 0.04, metals use albedo
|
||||
vec3 F0 = mix(vec3(0.04), albedo, metal);
|
||||
float shininess = mix(8.0, 256.0, 1.0 - rough);
|
||||
|
||||
for (int i = 0; i < lightCount; i++)
|
||||
{
|
||||
vec3 L = normalize(-lightDirs[i]);
|
||||
vec3 H = normalize(L + viewDir);
|
||||
float NdotL = max(dot(normal, L), 0.0);
|
||||
float NdotH = max(dot(normal, H), 0.0);
|
||||
float HdotV = max(dot(H, viewDir), 0.0);
|
||||
float diff = NdotL;
|
||||
float spec = pow(NdotH, shininess);
|
||||
// Schlick Fresnel using F0.rgb
|
||||
vec3 fresnel = F0 + (1.0 - F0) * pow(1.0 - HdotV, 5.0);
|
||||
vec3 specularColor = mix(fresnel, albedo * fresnel, metal);
|
||||
vec3 diffuse = albedo * lightColors[i] * diff * lightIntensities[i] * 1.5 * shadow;
|
||||
vec3 specular = specularColor * spec * lightIntensities[i] * shadow;
|
||||
diffuse *= (1.0 - fresnel * (1.0 - metal * 0.5));
|
||||
result += diffuse + specular;
|
||||
}
|
||||
|
||||
result = ACESFilm(result * 1.2);
|
||||
result = pow(result, vec3(1.0 / 2.2));
|
||||
finalColor = vec4(result, 1.0);
|
||||
}";
|
||||
|
||||
return Raylib.LoadShaderFromMemory(VertexSource, FragmentSource);
|
||||
}
|
||||
|
||||
private static Shader LoadShader()
|
||||
{
|
||||
@@ -846,7 +520,6 @@ void main()
|
||||
float HdotV = max(dot(H, viewDir), 0.0);
|
||||
float diff = NdotL;
|
||||
float spec = pow(NdotH, shininess);
|
||||
// Schlick Fresnel using F0.rgb (per-channel)
|
||||
vec3 fresnel = F0 + (1.0 - F0) * pow(1.0 - HdotV, 5.0);
|
||||
vec3 specularColor = mix(fresnel, albedo * fresnel, metal);
|
||||
vec3 diffuse = albedo * lightColors[i] * diff * lightIntensities[i] * atten * 1.5;
|
||||
@@ -876,10 +549,6 @@ void main()
|
||||
Raylib.UnloadTexture(texture);
|
||||
_textureCache.Clear();
|
||||
|
||||
Rlgl.UnloadTexture(_shadowDepthTex);
|
||||
Rlgl.UnloadFramebuffer(_shadowFbo);
|
||||
Raylib.UnloadShader(_shadowShader);
|
||||
Raylib.UnloadShader(_shadowReceiverShader);
|
||||
Raylib.UnloadShader(_shader);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user