diff --git a/examples/shaders/mesh_instancing/glsl330/base_lighting_instanced.vs b/examples/shaders/mesh_instancing/glsl330/base_lighting_instanced.vs new file mode 100644 index 0000000..d198efe --- /dev/null +++ b/examples/shaders/mesh_instancing/glsl330/base_lighting_instanced.vs @@ -0,0 +1,36 @@ +#version 330 + +// Input vertex attributes +in vec3 vertexPosition; +in vec2 vertexTexCoord; +in vec3 vertexNormal; +in vec4 vertexColor; + +in mat4 instanceTransform; + +// Input uniform values +uniform mat4 mvp; +uniform mat4 matNormal; + +// Output vertex attributes (to fragment shader) +out vec3 fragPosition; +out vec2 fragTexCoord; +out vec4 fragColor; +out vec3 fragNormal; + +// NOTE: Add here your custom variables + +void main() +{ + // Compute MVP for current instance + mat4 mvpi = mvp*instanceTransform; + + // Send vertex attributes to fragment shader + fragPosition = vec3(mvpi*vec4(vertexPosition, 1.0)); + fragTexCoord = vertexTexCoord; + fragColor = vertexColor; + fragNormal = normalize(vec3(matNormal*vec4(vertexNormal, 1.0))); + + // Calculate final vertex position + gl_Position = mvpi*vec4(vertexPosition, 1.0); +} diff --git a/examples/shaders/mesh_instancing/glsl330/lighting.fs b/examples/shaders/mesh_instancing/glsl330/lighting.fs new file mode 100644 index 0000000..6877c1c --- /dev/null +++ b/examples/shaders/mesh_instancing/glsl330/lighting.fs @@ -0,0 +1,82 @@ +#version 330 + +// Input vertex attributes (from vertex shader) +in vec3 fragPosition; +in vec2 fragTexCoord; +in vec4 fragColor; +in vec3 fragNormal; + +// Input uniform values +uniform sampler2D texture0; +uniform vec4 colDiffuse; + +// Output fragment color +out vec4 finalColor; + +// NOTE: Add here your custom variables + +#define MAX_LIGHTS 4 +#define LIGHT_DIRECTIONAL 0 +#define LIGHT_POINT 1 + +struct MaterialProperty { + vec3 color; + int useSampler; + sampler2D sampler; +}; + +struct Light { + int enabled; + int type; + vec3 position; + vec3 target; + vec4 color; +}; + +// Input lighting values +uniform Light lights[MAX_LIGHTS]; +uniform vec4 ambient; +uniform vec3 viewPos; + +void main() +{ + // Texel color fetching from texture sampler + vec4 texelColor = texture(texture0, fragTexCoord); + vec3 lightDot = vec3(0.0); + vec3 normal = normalize(fragNormal); + vec3 viewD = normalize(viewPos - fragPosition); + vec3 specular = vec3(0.0); + + // NOTE: Implement here your fragment shader code + + for (int i = 0; i < MAX_LIGHTS; i++) + { + if (lights[i].enabled == 1) + { + vec3 light = vec3(0.0); + + if (lights[i].type == LIGHT_DIRECTIONAL) + { + light = -normalize(lights[i].target - lights[i].position); + } + + if (lights[i].type == LIGHT_POINT) + { + light = normalize(lights[i].position - fragPosition); + } + + float NdotL = max(dot(normal, light), 0.0); + lightDot += lights[i].color.rgb*NdotL; + + float specCo = 0.0; + if (NdotL > 0.0) specCo = pow(max(0.0, dot(viewD, reflect(-(light), normal))), 16.0); // 16 refers to shine + specular += specCo; + } + } + + finalColor = (texelColor*((colDiffuse + vec4(specular, 1.0))*vec4(lightDot, 1.0))); + finalColor += texelColor*(ambient/10.0)*colDiffuse; + + // Gamma correction + finalColor = pow(finalColor, vec4(1.0/2.2)); +} diff --git a/examples/shaders/mesh_instancing/light.go b/examples/shaders/mesh_instancing/light.go new file mode 100644 index 0000000..b38aae2 --- /dev/null +++ b/examples/shaders/mesh_instancing/light.go @@ -0,0 +1,92 @@ +package main + +import ( + "fmt" + "reflect" + "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 locations + enabledLoc int32 + typeLoc int32 + posLoc int32 + targetLoc int32 + colorLoc int32 +} + +const maxLightsCount = 4 + +var lightCount = 0 + +func NewLight( + 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 +} + +// Send light properties to shader +func (lt *Light) UpdateValues() { + // not pretty -_- + // need nicer api + sh := &reflect.SliceHeader{ + Len: 4, + Cap: 4, + } + + // Send to shader light enabled state and type + sh.Data = uintptr(unsafe.Pointer(<.enabled)) + rl.SetShaderValue(lt.shader, lt.enabledLoc, *(*[]float32)(unsafe.Pointer(sh)), rl.ShaderUniformInt) + + // Send to shader light position values + sh.Data = uintptr(unsafe.Pointer(<.lightType)) + rl.SetShaderValue(lt.shader, lt.posLoc, []float32{lt.position.X, lt.position.Y, lt.position.Z}, rl.ShaderUniformVec3) + + // Send to shader light target target values + rl.SetShaderValue(lt.shader, lt.targetLoc, []float32{lt.target.X, lt.target.Y, lt.target.Z}, rl.ShaderUniformVec3) + + // Send to shader light color values + rl.SetShaderValue(lt.shader, lt.colorLoc, + []float32{float32(lt.color.R) / 255, float32(lt.color.G) / 255, float32(lt.color.B) / 255, float32(lt.color.A) / 255}, + rl.ShaderUniformVec4) +} diff --git a/examples/shaders/mesh_instancing/main.go b/examples/shaders/mesh_instancing/main.go new file mode 100644 index 0000000..a61c3f7 --- /dev/null +++ b/examples/shaders/mesh_instancing/main.go @@ -0,0 +1,253 @@ +package main + +import ( + "fmt" + "math" + + rl "github.com/gen2brain/raylib-go/raylib" +) + +const MAX_INSTANCES = 100000 + +func main() { + var ( + screenWidth = int32(800) // Framebuffer width + screenHeight = int32(450) // Framebuffer height + fps = 60 // Frames per second + speed = 30 // Speed of jump animation + groups = 2 // Count of separate groups jumping around + amp = float32(10) // Maximum amplitude of jump + variance = float32(0.8) // Global variance in jump height + loop = float32(0.0) // Individual cube's computed loop timer + textPositionY int32 = 300 + framesCounter = 0 + ) + + rl.SetConfigFlags(rl.FlagMsaa4xHint) // Enable Multi Sampling Anti Aliasing 4x (if available) + rl.InitWindow(screenWidth, screenHeight, "raylib [shaders] example - mesh instancing") + + // Define the camera to look into our 3d world + camera := rl.Camera{ + Position: rl.NewVector3(-125.0, 125.0, -125.0), + Target: rl.NewVector3(0.0, 0.0, 0.0), + Up: rl.NewVector3(0.0, 1.0, 0.0), + Fovy: 45.0, + Projection: rl.CameraPerspective, + } + + cube := rl.GenMeshCube(1.0, 1.0, 1.0) + + rotations := make([]rl.Matrix, MAX_INSTANCES) // Rotation state of instances + rotationsInc := make([]rl.Matrix, MAX_INSTANCES) // Per-frame rotation animation of instances + translations := make([]rl.Matrix, MAX_INSTANCES) // Locations of instances + + // Scatter random cubes around + for i := 0; i < MAX_INSTANCES; i++ { + x := float32(rl.GetRandomValue(-50, 50)) + y := float32(rl.GetRandomValue(-50, 50)) + z := float32(rl.GetRandomValue(-50, 50)) + translations[i] = rl.MatrixTranslate(x, y, z) + + x = float32(rl.GetRandomValue(0, 360)) + y = float32(rl.GetRandomValue(0, 360)) + z = float32(rl.GetRandomValue(0, 360)) + axis := rl.Vector3Normalize(rl.NewVector3(x, y, z)) + angle := float32(rl.GetRandomValue(0, 10)) * rl.Deg2rad + + rotationsInc[i] = rl.MatrixRotate(axis, angle) + rotations[i] = rl.MatrixIdentity() + } + + transforms := make([]rl.Matrix, MAX_INSTANCES) + + shader := rl.LoadShader("glsl330/base_lighting_instanced.vs", "glsl330/lighting.fs") + shader.UpdateLocation(rl.LocMatrixMvp, rl.GetShaderLocation(shader, "mvp")) + shader.UpdateLocation(rl.LocVectorView, rl.GetShaderLocation(shader, "viewPos")) + shader.UpdateLocation(rl.LocMatrixModel, rl.GetShaderLocationAttrib(shader, "instanceTransform")) + + // ambient light level + ambientLoc := rl.GetShaderLocation(shader, "ambient") + rl.SetShaderValue(shader, ambientLoc, []float32{0.2, 0.2, 0.2, 1.0}, rl.ShaderUniformVec4) + NewLight(LightTypeDirectional, rl.NewVector3(50.0, 50.0, 0.0), rl.Vector3Zero(), rl.White, shader) + + material := rl.LoadMaterialDefault() + material.Shader = shader + mmap := material.GetMap(rl.MapDiffuse) + mmap.Color = rl.Red + + rl.SetCameraMode(camera, rl.CameraOrbital) + + rl.SetTargetFPS(int32(fps)) + for !rl.WindowShouldClose() { + // Update + //---------------------------------------------------------------------------------- + + textPositionY = 300 + framesCounter++ + + if rl.IsKeyDown(rl.KeyUp) { + amp += 0.5 + } + if rl.IsKeyDown(rl.KeyDown) { + if amp <= 1 { + amp = 1 + } else { + amp -= 1 + } + } + if rl.IsKeyDown(rl.KeyLeft) { + if variance < 0 { + variance = 0 + } else { + variance -= 0.01 + } + } + if rl.IsKeyDown(rl.KeyRight) { + if variance > 1 { + variance = 1 + } else { + variance += 0.01 + } + } + if rl.IsKeyDown(rl.KeyOne) { + groups = 1 + } + if rl.IsKeyDown(rl.KeyTwo) { + groups = 2 + } + if rl.IsKeyDown(rl.KeyThree) { + groups = 3 + } + if rl.IsKeyDown(rl.KeyFour) { + groups = 4 + } + if rl.IsKeyDown(rl.KeyFive) { + groups = 5 + } + if rl.IsKeyDown(rl.KeySix) { + groups = 6 + } + if rl.IsKeyDown(rl.KeySeven) { + groups = 7 + } + if rl.IsKeyDown(rl.KeyEight) { + groups = 8 + } + if rl.IsKeyDown(rl.KeyNine) { + groups = 9 + } + if rl.IsKeyDown(rl.KeyW) { + groups = 7 + amp = 25 + speed = 18 + variance = float32(0.70) + } + if rl.IsKeyDown(rl.KeyEqual) { + if float32(speed) <= float32(fps)*0.25 { + speed = int(float32(fps) * 0.25) + } else { + speed = int(float32(speed) * 0.95) + } + } + if rl.IsKeyDown(rl.KeyKpAdd) { + if float32(speed) <= float32(fps)*0.25 { + speed = int(float32(fps) * 0.25) + } else { + speed = int(float32(speed) * 0.95) + } + } + if rl.IsKeyDown(rl.KeyMinus) { + speed = int(math.Max(float64(speed)*1.02, float64(speed)+1)) + } + if rl.IsKeyDown(rl.KeyKpSubtract) { + speed = int(math.Max(float64(speed)*1.02, float64(speed)+1)) + } + + // Update the light shader with the camera view position + rl.SetShaderValue(shader, shader.GetLocation(rl.LocVectorView), + []float32{camera.Position.X, camera.Position.Y, camera.Position.Z}, rl.ShaderUniformVec3) + + // Apply per-instance transformations + for i := 0; i < MAX_INSTANCES; i++ { + rotations[i] = rl.MatrixMultiply(rotations[i], rotationsInc[i]) + transforms[i] = rl.MatrixMultiply(rotations[i], translations[i]) + + // Get the animation cycle's framesCounter for this instance + loop = float32((framesCounter+int(float32(i%groups)/float32(groups)*float32(speed)))%speed) / float32(speed) + + // Calculate the y according to loop cycle + y := float32(math.Sin(float64(loop)*rl.Pi*2)) * amp * + ((1 - variance) + (float32(variance) * float32(i%(groups*10)) / float32(groups*10))) + + // Clamp to floor + if y < 0 { + y = 0 + } + + transforms[i] = rl.MatrixMultiply(transforms[i], rl.MatrixTranslate(0.0, y, 0.0)) + } + + rl.UpdateCamera(&camera) // Update camera + //---------------------------------------------------------------------------------- + + // Draw + //---------------------------------------------------------------------------------- + rl.BeginDrawing() + { + rl.ClearBackground(rl.RayWhite) + + rl.BeginMode3D(camera) + //rl.DrawMesh(cube, material, rl.MatrixIdentity()) + rl.DrawMeshInstanced(cube, material, transforms, MAX_INSTANCES) + rl.EndMode3D() + + rl.DrawText("A CUBE OF DANCING CUBES!", 490, 10, 20, rl.Maroon) + rl.DrawText("PRESS KEYS:", 10, textPositionY, 20, rl.Black) + + textPositionY += 25 + rl.DrawText("1 - 9", 10, textPositionY, 10, rl.Black) + rl.DrawText(": Number of groups", 50, textPositionY, 10, rl.Black) + rl.DrawText(fmt.Sprintf(": %d", groups), 160, textPositionY, 10, rl.Black) + + textPositionY += 15 + rl.DrawText("UP", 10, textPositionY, 10, rl.Black) + rl.DrawText(": increase amplitude", 50, textPositionY, 10, rl.Black) + rl.DrawText(fmt.Sprintf(": %.2f", amp), 160, textPositionY, 10, rl.Black) + + textPositionY += 15 + rl.DrawText("DOWN", 10, textPositionY, 10, rl.Black) + rl.DrawText(": decrease amplitude", 50, textPositionY, 10, rl.Black) + + textPositionY += 15 + rl.DrawText("LEFT", 10, textPositionY, 10, rl.Black) + rl.DrawText(": decrease variance", 50, textPositionY, 10, rl.Black) + rl.DrawText(fmt.Sprintf(": %.2f", variance), 160, textPositionY, 10, rl.Black) + + textPositionY += 15 + rl.DrawText("RIGHT", 10, textPositionY, 10, rl.Black) + rl.DrawText(": increase variance", 50, textPositionY, 10, rl.Black) + + textPositionY += 15 + rl.DrawText("+/=", 10, textPositionY, 10, rl.Black) + rl.DrawText(": increase speed", 50, textPositionY, 10, rl.Black) + rl.DrawText(fmt.Sprintf(": %d = %f loops/sec", speed, float32(fps)/float32(speed)), 160, textPositionY, 10, rl.Black) + + textPositionY += 15 + rl.DrawText("-", 10, textPositionY, 10, rl.Black) + rl.DrawText(": decrease speed", 50, textPositionY, 10, rl.Black) + + textPositionY += 15 + rl.DrawText("W", 10, textPositionY, 10, rl.Black) + rl.DrawText(": Wild setup!", 50, textPositionY, 10, rl.Black) + + rl.DrawFPS(10, 10) + } + rl.EndDrawing() + //---------------------------------------------------------------------------------- + } + + // De-Initialization + //-------------------------------------------------------------------------------------- + rl.CloseWindow() // Close window and OpenGL context + //-------------------------------------------------------------------------------------- +} diff --git a/raylib/models.go b/raylib/models.go index fb04702..dbaee2f 100644 --- a/raylib/models.go +++ b/raylib/models.go @@ -400,6 +400,16 @@ func DrawBillboardRec(camera Camera, texture Texture2D, sourceRec Rectangle, cen C.DrawBillboardRec(*ccamera, *ctexture, *csourceRec, *ccenter, *csize, *ctint) } +// DrawMesh - Draw a single mesh +func DrawMesh(mesh Mesh, material Material, transform Matrix) { + C.DrawMesh(*mesh.cptr(), *material.cptr(), *transform.cptr()) +} + +// DrawMeshInstanced - Draw mesh with instanced rendering +func DrawMeshInstanced(mesh Mesh, material Material, transforms []Matrix, instances int) { + C.DrawMeshInstanced(*mesh.cptr(), *material.cptr(), transforms[0].cptr(), C.int(instances)) +} + // GetMeshBoundingBox - Compute mesh bounding box limits func GetMeshBoundingBox(mesh Mesh) BoundingBox { cmesh := mesh.cptr() diff --git a/raylib/raylib.go b/raylib/raylib.go index a5b8087..845df84 100644 --- a/raylib/raylib.go +++ b/raylib/raylib.go @@ -716,8 +716,10 @@ const ( ) // Shader location point type +type LocationPointType int32 + const ( - LocVertexPosition = iota + LocVertexPosition LocationPointType = iota LocVertexTexcoord01 LocVertexTexcoord02 LocVertexNormal @@ -746,8 +748,10 @@ const ( ) // Material map type +type MaterialMapType int32 + const ( - MapAlbedo = iota + MapAlbedo MaterialMapType = iota MapMetalness MapNormal MapRoughness @@ -830,6 +834,11 @@ func newMaterialFromPointer(ptr unsafe.Pointer) Material { return *(*Material)(ptr) } +// GetMap - Get pointer to MaterialMap by map type +func (mt Material) GetMap(index MaterialMapType) *MaterialMap { + return (*MaterialMap)(unsafe.Pointer(uintptr(unsafe.Pointer(mt.Maps)) + uintptr(index)*uintptr(unsafe.Sizeof(MaterialMap{})))) +} + // MaterialMap type type MaterialMap struct { // Texture @@ -920,6 +929,16 @@ func newShaderFromPointer(ptr unsafe.Pointer) Shader { return *(*Shader)(ptr) } +// GetLocation - Get shader value's location +func (sh Shader) GetLocation(index LocationPointType) int32 { + return *(*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(sh.Locs)) + uintptr(index*4))) +} + +// UpdateLocation - Update shader value's location +func (sh Shader) UpdateLocation(index LocationPointType, loc int32) { + *(*int32)(unsafe.Pointer(uintptr(unsafe.Pointer(sh.Locs)) + uintptr(index*4))) = loc +} + // CharInfo - Font character info type CharInfo struct { // Character value (Unicode) diff --git a/raylib/raymath.go b/raylib/raymath.go index b211063..0ab66fd 100644 --- a/raylib/raymath.go +++ b/raylib/raymath.go @@ -673,22 +673,22 @@ func MatrixScale(x, y, z float32) Matrix { func MatrixMultiply(left, right Matrix) Matrix { var result Matrix - result.M0 = right.M0*left.M0 + right.M1*left.M4 + right.M2*left.M8 + right.M3*left.M12 - result.M1 = right.M0*left.M1 + right.M1*left.M5 + right.M2*left.M9 + right.M3*left.M13 - result.M2 = right.M0*left.M2 + right.M1*left.M6 + right.M2*left.M10 + right.M3*left.M14 - result.M3 = right.M0*left.M3 + right.M1*left.M7 + right.M2*left.M11 + right.M3*left.M15 - result.M4 = right.M4*left.M0 + right.M5*left.M4 + right.M6*left.M8 + right.M7*left.M12 - result.M5 = right.M4*left.M1 + right.M5*left.M5 + right.M6*left.M9 + right.M7*left.M13 - result.M6 = right.M4*left.M2 + right.M5*left.M6 + right.M6*left.M10 + right.M7*left.M14 - result.M7 = right.M4*left.M3 + right.M5*left.M7 + right.M6*left.M11 + right.M7*left.M15 - result.M8 = right.M8*left.M0 + right.M9*left.M4 + right.M10*left.M8 + right.M11*left.M12 - result.M9 = right.M8*left.M1 + right.M9*left.M5 + right.M10*left.M9 + right.M11*left.M13 - result.M10 = right.M8*left.M2 + right.M9*left.M6 + right.M10*left.M10 + right.M11*left.M14 - result.M11 = right.M8*left.M3 + right.M9*left.M7 + right.M10*left.M11 + right.M11*left.M15 - result.M12 = right.M12*left.M0 + right.M13*left.M4 + right.M14*left.M8 + right.M15*left.M12 - result.M13 = right.M12*left.M1 + right.M13*left.M5 + right.M14*left.M9 + right.M15*left.M13 - result.M14 = right.M12*left.M2 + right.M13*left.M6 + right.M14*left.M10 + right.M15*left.M14 - result.M15 = right.M12*left.M3 + right.M13*left.M7 + right.M14*left.M11 + right.M15*left.M15 + result.M0 = left.M0*right.M0 + left.M1*right.M4 + left.M2*right.M8 + left.M3*right.M12 + result.M1 = left.M0*right.M1 + left.M1*right.M5 + left.M2*right.M9 + left.M3*right.M13 + result.M2 = left.M0*right.M2 + left.M1*right.M6 + left.M2*right.M10 + left.M3*right.M14 + result.M3 = left.M0*right.M3 + left.M1*right.M7 + left.M2*right.M11 + left.M3*right.M15 + result.M4 = left.M4*right.M0 + left.M5*right.M4 + left.M6*right.M8 + left.M7*right.M12 + result.M5 = left.M4*right.M1 + left.M5*right.M5 + left.M6*right.M9 + left.M7*right.M13 + result.M6 = left.M4*right.M2 + left.M5*right.M6 + left.M6*right.M10 + left.M7*right.M14 + result.M7 = left.M4*right.M3 + left.M5*right.M7 + left.M6*right.M11 + left.M7*right.M15 + result.M8 = left.M8*right.M0 + left.M9*right.M4 + left.M10*right.M8 + left.M11*right.M12 + result.M9 = left.M8*right.M1 + left.M9*right.M5 + left.M10*right.M9 + left.M11*right.M13 + result.M10 = left.M8*right.M2 + left.M9*right.M6 + left.M10*right.M10 + left.M11*right.M14 + result.M11 = left.M8*right.M3 + left.M9*right.M7 + left.M10*right.M11 + left.M11*right.M15 + result.M12 = left.M12*right.M0 + left.M13*right.M4 + left.M14*right.M8 + left.M15*right.M12 + result.M13 = left.M12*right.M1 + left.M13*right.M5 + left.M14*right.M9 + left.M15*right.M13 + result.M14 = left.M12*right.M2 + left.M13*right.M6 + left.M14*right.M10 + left.M15*right.M14 + result.M15 = left.M12*right.M3 + left.M13*right.M7 + left.M14*right.M11 + left.M15*right.M15 return result }