Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions examples/shaders/cel_shading/cel.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#version 330

in vec3 fragPosition;
in vec2 fragTexCoord;
in vec4 fragColor;
in vec3 fragNormal;

// Raylib standard uniforms
uniform sampler2D texture0;
uniform vec4 colDiffuse;

// View position for future specular / fresnel use.
uniform vec3 viewPos;

// Number of discrete toon bands (2 = hard binary, 10 = default, 20 = near-smooth).
uniform float numBands;

// rlights.h compatible light block.
struct Light {
int enabled;
int type; // 0 = directional, 1 = point
vec3 position;
vec3 target;
vec4 color;
float attenuation;
};
uniform Light lights[4];

out vec4 finalColor;

void main() {
vec4 texColor = texture(texture0, fragTexCoord);
vec3 baseColor = texColor.rgb * fragColor.rgb * colDiffuse.rgb;
vec3 norm = normalize(fragNormal);

float lightAccum = 0.08; // ambient floor

for (int i = 0; i < 4; i++) {
if (lights[i].enabled == 0) continue;

vec3 lightDir;
if (lights[i].type == 0) {
// Directional: direction is from position toward target.
lightDir = normalize(lights[i].position - lights[i].target);
} else {
// Point: direction from surface to light.
lightDir = normalize(lights[i].position - fragPosition);
}

float NdotL = max(dot(norm, lightDir), 0.0);

// Quantize NdotL into numBands discrete steps.
// min() guards against NdotL == 1.0 producing an out-of-range index.
float quantized = min(floor(NdotL * numBands), numBands - 1.0) / (numBands - 1.0);
lightAccum += quantized * lights[i].color.r;
}

lightAccum = clamp(lightAccum, 0.0, 1.0);
finalColor = vec4(baseColor * lightAccum, texColor.a * colDiffuse.a);
}
25 changes: 25 additions & 0 deletions examples/shaders/cel_shading/cel.vs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#version 330

// Raylib standard attributes
in vec3 vertexPosition;
in vec2 vertexTexCoord;
in vec3 vertexNormal;
in vec4 vertexColor;

// Raylib standard uniforms
uniform mat4 mvp;
uniform mat4 matModel;
uniform mat4 matNormal;

out vec3 fragPosition;
out vec2 fragTexCoord;
out vec4 fragColor;
out vec3 fragNormal;

void main() {
fragPosition = vec3(matModel * vec4(vertexPosition, 1.0));
fragTexCoord = vertexTexCoord;
fragColor = vertexColor;
fragNormal = normalize(vec3(matNormal * vec4(vertexNormal, 0.0)));
gl_Position = mvp * vec4(vertexPosition, 1.0);
}
89 changes: 89 additions & 0 deletions examples/shaders/cel_shading/light.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package main

import (
"fmt"
"unsafe"

rl "github.com/gen2brain/raylib-go/raylib"
)

type LightType int32

const (
LightTypeDirectional LightType = iota
LightTypePoint
)

type Light struct {
Shader rl.Shader
LightType LightType
Position rl.Vector3
Target rl.Vector3
Color rl.Color
Enabled int32

// Shader location indexes
enabledLoc int32
typeLoc int32
posLoc int32
targetLoc int32
colorLoc int32
}

const maxLightsCount = 4

var lightCount = 0

// CreateLight initializes a light and sends initial values to the shader
func CreateLight(
lightType LightType,
position, target rl.Vector3,
color rl.Color,
shader rl.Shader,
) Light {
light := Light{
Shader: shader,
}

if lightCount < maxLightsCount {
light.Enabled = 1
light.LightType = lightType
light.Position = position
light.Target = target
light.Color = color

light.enabledLoc = rl.GetShaderLocation(shader, fmt.Sprintf("lights[%d].enabled", lightCount))
light.typeLoc = rl.GetShaderLocation(shader, fmt.Sprintf("lights[%d].type", lightCount))
light.posLoc = rl.GetShaderLocation(shader, fmt.Sprintf("lights[%d].position", lightCount))
light.targetLoc = rl.GetShaderLocation(shader, fmt.Sprintf("lights[%d].target", lightCount))
light.colorLoc = rl.GetShaderLocation(shader, fmt.Sprintf("lights[%d].color", lightCount))

light.UpdateValues()
lightCount++
}

return light
}

// NewLight alias for CreateLight
func NewLight(
lightType LightType,
position, target rl.Vector3,
color rl.Color,
shader rl.Shader,
) Light {
return CreateLight(lightType, position, target, color, shader)
}

// UpdateValues sends updated light attributes to the GLSL shader
func (lt *Light) UpdateValues() {
rl.SetShaderValue(lt.Shader, lt.enabledLoc, unsafe.Slice((*float32)(unsafe.Pointer(&lt.Enabled)), 1), rl.ShaderUniformInt)
rl.SetShaderValue(lt.Shader, lt.typeLoc, unsafe.Slice((*float32)(unsafe.Pointer(&lt.LightType)), 1), rl.ShaderUniformInt)

rl.SetShaderValue(lt.Shader, lt.posLoc, []float32{lt.Position.X, lt.Position.Y, lt.Position.Z}, rl.ShaderUniformVec3)
rl.SetShaderValue(lt.Shader, lt.targetLoc, []float32{lt.Target.X, lt.Target.Y, lt.Target.Z}, rl.ShaderUniformVec3)

rl.SetShaderValue(lt.Shader, lt.colorLoc,
[]float32{float32(lt.Color.R) / 255.0, float32(lt.Color.G) / 255.0, float32(lt.Color.B) / 255.0, float32(lt.Color.A) / 255.0},
rl.ShaderUniformVec4)
}
157 changes: 157 additions & 0 deletions examples/shaders/cel_shading/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package main

import (
"fmt"
"math"
"unsafe"

rl "github.com/gen2brain/raylib-go/raylib"
)

const (
cullFaceFront = 0 // RL_CULL_FACE_FRONT
cullFaceBack = 1 // RL_CULL_FACE_BACK
)

func main() {
const screenWidth = 800
const screenHeight = 450

rl.SetConfigFlags(rl.FlagMsaa4xHint)
rl.InitWindow(screenWidth, screenHeight, "raylib [shaders] example - cel shading")
defer rl.CloseWindow()

camera := rl.Camera3D{}
camera.Position = rl.NewVector3(9.0, 6.0, 9.0)
camera.Target = rl.NewVector3(0.0, 1.0, 0.0)
camera.Up = rl.NewVector3(0.0, 1.0, 0.0)
camera.Fovy = 45.0
camera.Projection = rl.CameraPerspective

model := rl.LoadModel("old_car_new.glb")
defer rl.UnloadModel(model)

materials := unsafe.Slice(model.Materials, model.MaterialCount)

celShader := rl.LoadShader("cel.vs", "cel.fs")
defer rl.UnloadShader(celShader)

celShaderLocs := unsafe.Slice(celShader.Locs, 32)
celShaderLocs[rl.ShaderLocVectorView] = rl.GetShaderLocation(celShader, "viewPos")

defaultShader := materials[0].Shader
materials[0].Shader = celShader

numBands := float32(10.0)
numBandsLoc := rl.GetShaderLocation(celShader, "numBands")
rl.SetShaderValue(celShader, numBandsLoc, []float32{numBands}, rl.ShaderUniformFloat)

outlineShader := rl.LoadShader("outline_hull.vs", "outline_hull.fs")
defer rl.UnloadShader(outlineShader)

outlineThicknessLoc := rl.GetShaderLocation(outlineShader, "outlineThickness")

lights := make([]Light, maxLightsCount)
lights[0] = CreateLight(LightTypeDirectional, rl.NewVector3(50.0, 50.0, 50.0), rl.Vector3Zero(), rl.White, celShader)

celEnabled := true
outlineEnabled := true

rl.SetTargetFPS(60)

for !rl.WindowShouldClose() {
rl.UpdateCamera(&camera, rl.CameraOrbital)

cameraPos := []float32{camera.Position.X, camera.Position.Y, camera.Position.Z}
rl.SetShaderValue(celShader, celShaderLocs[rl.ShaderLocVectorView], cameraPos, rl.ShaderUniformVec3)

if rl.IsKeyPressed(rl.KeyZ) {
celEnabled = !celEnabled
if celEnabled {
materials[0].Shader = celShader
} else {
materials[0].Shader = defaultShader
}
}

if rl.IsKeyPressed(rl.KeyC) {
outlineEnabled = !outlineEnabled
}

if rl.IsKeyPressed(rl.KeyE) || rl.IsKeyPressedRepeat(rl.KeyE) {
numBands = rl.Clamp(numBands+1.0, 2.0, 20.0)
}
if rl.IsKeyPressed(rl.KeyQ) || rl.IsKeyPressedRepeat(rl.KeyQ) {
numBands = rl.Clamp(numBands-1.0, 2.0, 20.0)
}
rl.SetShaderValue(celShader, numBandsLoc, []float32{numBands}, rl.ShaderUniformFloat)

t := float32(rl.GetTime())
lights[0].Position = rl.NewVector3(
float32(math.Sin(float64(-t*0.3)))*5.0,
5.0,
float32(math.Cos(float64(-t*0.3)))*5.0,
)

for i := 0; i < maxLightsCount; i++ {
if lights[i].Enabled == 1 {
lights[i].UpdateValues()
}
}

// Draw
rl.BeginDrawing()

rl.ClearBackground(rl.RayWhite)

rl.BeginMode3D(camera)

if outlineEnabled {

thickness := float32(0.005)
rl.SetShaderValue(outlineShader, outlineThicknessLoc, []float32{thickness}, rl.ShaderUniformFloat)

rl.SetCullFace(cullFaceFront)

materials[0].Shader = outlineShader

rl.DrawModel(model, rl.Vector3Zero(), 0.75, rl.White)

if celEnabled {
materials[0].Shader = celShader
} else {
materials[0].Shader = defaultShader
}

rl.SetCullFace(cullFaceBack)
}

rl.DrawModel(model, rl.Vector3Zero(), 0.75, rl.White)
rl.DrawSphereEx(lights[0].Position, 0.2, 50, 50, rl.Yellow)
rl.DrawGrid(10, 10.0)

rl.EndMode3D()

rl.DrawFPS(10, 10)

celStatus := "OFF"
celColor := rl.DarkGray
if celEnabled {
celStatus = "ON"
celColor = rl.DarkGreen
}
rl.DrawText(fmt.Sprintf("Cel: %s [Z]", celStatus), 10, 65, 20, celColor)

outlineStatus := "OFF"
outlineColor := rl.DarkGray
if outlineEnabled {
outlineStatus = "ON"
outlineColor = rl.DarkGreen
}
rl.DrawText(fmt.Sprintf("Outline: %s [C]", outlineStatus), 10, 90, 20, outlineColor)

rl.DrawText(fmt.Sprintf("Bands: %.0f [Q/E]", numBands), 10, 115, 20, rl.DarkGray)

rl.EndDrawing()
}
}
Binary file added examples/shaders/cel_shading/old_car_new.glb
Binary file not shown.
7 changes: 7 additions & 0 deletions examples/shaders/cel_shading/outline_hull.fs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#version 330

out vec4 finalColor;

void main() {
finalColor = vec4(0.05, 0.05, 0.05, 1.0);
}
15 changes: 15 additions & 0 deletions examples/shaders/cel_shading/outline_hull.vs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#version 330

in vec3 vertexPosition;
in vec3 vertexNormal;
in vec2 vertexTexCoord;
in vec4 vertexColor;

uniform mat4 mvp;
uniform float outlineThickness;

void main() {
// Extrude vertex along its normal to create the hull.
vec3 extruded = vertexPosition + vertexNormal * outlineThickness;
gl_Position = mvp * vec4(extruded, 1.0);
}
Loading
Loading